内核贡献
第19章:Linux 内核开发与社区贡献
向 Linux 内核提交补丁是许多工程师的技术里程碑。内核社区规模庞大、流程严格,但同时也对新手友好——只要你遵循既定规范。本章覆盖从获取源码、编译内核、理解编码规范,到用 checkpatch.pl 检查代码、用 git format-patch 生成补丁、通过 git send-email 发送至 LKML,直至获得 maintainer 认可的完整流程。
1. 内核开发入门:门槛与机会
内核开发与应用开发的本质区别在于运行环境:内核代码在没有标准库、没有内存保护、没有浮点运算的特权模式下运行。一个空指针解引用不会产生 segfault,而是直接触发内核崩溃(panic)。这要求开发者对硬件和操作系统原理有深入理解。
尽管门槛不低,内核社区对新手友好的贡献类型有很多。新手不需要从编写新驱动或实现新子系统开始,以下是推荐的入门路径:
- 文档改进 — 修复错字、补充示例、翻译文档,风险极低,maintainer 审核快
- drivers/staging 清理 — staging 目录中的驱动质量参差,代码中到处标注了 TODO,是新手练手的理想场所
- checkpatch 警告修复 — 运行 checkpatch.pl 扫描整棵树,修复代码风格警告
- Coccinelle 语义补丁 — 用 Coccinelle 脚本自动化批量代码变换(如将 kzalloc+memset 合并为 kcalloc)
- sparse 静态分析修复 —
make C=1调用 sparse 检查类型注解,修复发现的问题
回报: 成为内核贡献者意味着你的代码运行在全球数十亿台设备上。Kernel Newbies 统计显示,第一个 patch 被接受后,绝大多数贡献者会持续投入。贡献记录也是求职时的有力证明。
2. 获取内核源码
Linux 内核有多个官方代码树,新手应了解各自的定位:
| 代码树 | URL | 用途 |
|---|---|---|
| mainline | kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git | Linus 的主线树,发布 rc 版本 |
| linux-next | kernel.org/pub/scm/linux/kernel/git/next/linux-next.git | 各子系统 next 分支的集成树,补丁应基于此树 |
| stable | kernel.org/pub/scm/linux/kernel/git/stable/linux.git | 稳定版本维护,回移重要修复 |
| 子系统树 | 各 maintainer 各自维护 | 如 net-next(网络)、drm-next(显卡) |
# 克隆 linux-next(使用 --depth=1 节省时间,完整历史约 4GB)
git clone --depth=1 \
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git \
linux-next
cd linux-next
# 如果后续需要完整历史(用于 git log / git blame)
git fetch --unshallow
# 添加 stable 树作为远程仓库
git remote add stable \
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
# 查看 tag 列表(内核版本号)
git tag -l 'v6.*' | sort -V | tail -20
3. 编译内核
安装编译依赖
# Debian / Ubuntu
sudo apt-get install -y \
build-essential libelf-dev libssl-dev \
flex bison bc dwarves pahole \
libncurses-dev pkg-config
# Fedora / RHEL
sudo dnf install -y \
gcc make flex bison elfutils-libelf-devel \
openssl-devel ncurses-devel bc dwarves
内核配置
# 方式1:图形化 TUI 菜单(推荐新手)
make menuconfig
# 方式2:使用架构默认配置(适合快速验证补丁)
make defconfig
# 方式3:基于当前运行内核的配置(只编译已加载模块,速度最快)
make localmodconfig
# 之后把当前内核 config 复制过来
cp /boot/config-$(uname -r) .config
make olddefconfig # 对新增选项采用默认值
# 仅修改单个配置项(不打开菜单)
./scripts/config --enable CONFIG_DEBUG_INFO
./scripts/config --disable CONFIG_SECURITY_SELINUX
make olddefconfig
编译与安装
# 并行编译(nproc 输出 CPU 核心数,编译时间随机器而异,约10分钟到1小时)
make -j$(nproc)
# 用 ccache 加速二次编译(首次编译后效果明显)
sudo apt-get install -y ccache
CC="ccache gcc" make -j$(nproc)
# 安装内核模块
sudo make modules_install
# 安装内核镜像(同时更新 initramfs 和 GRUB)
sudo make install
# 或手动复制
sudo cp arch/x86/boot/bzImage /boot/vmlinuz-$(make kernelrelease)
sudo update-grub
# 验证新内核版本
cat include/generated/utsrelease.h
注意: 在虚拟机或备用机器上测试自编译内核,切勿在生产机器上直接替换内核。若新内核无法启动,可在 GRUB 菜单选择旧内核回退。
4. 内核编码规范
内核编码规范文档位于 Documentation/process/coding-style.rst,是所有贡献者必读的文件。违反规范的补丁会被 maintainer 直接打回,甚至不会给任何解释。
缩进与行长
/* 内核使用 TAB 缩进,宽度为 8 个字符(非空格!) */
/* 行长限制从历史的 80 字符放宽到了 100 字符 */
/* 正确 */
int kernel_function(struct device *dev, unsigned long flags)
{
if (flags & SOME_FLAG) {
do_something(dev);
return 0;
}
return -EINVAL;
}
/* 错误:使用空格缩进 */
int bad_function(void) {
return 0; /* 这是空格,不是 TAB */
}
花括号位置
/* 函数体的左花括号独占一行(K&R 风格的内核变体) */
int my_function(int arg)
{
/* 函数体 */
}
/* 控制语句(if/for/while)左花括号在同一行 */
if (condition) {
do_something();
} else {
do_other();
}
/* 单行 if 不要加花括号,除非 else 分支需要 */
if (err)
return err;
命名规范
- 函数/变量:
lowercase_with_underscores - 宏/常量:
UPPERCASE_WITH_UNDERSCORES - 结构体类型:
struct my_struct(不用 typedef,除非有明确理由) - 全局变量: 应极力避免;若必须使用,加模块前缀
goto 的合理使用
/* 内核中 goto 用于错误路径清理,是推荐做法 */
int probe_device(struct platform_device *pdev)
{
struct my_priv *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
ret = -ENOMEM;
goto err_alloc;
}
ret = request_irq(priv->irq, my_irq_handler, 0, "mydev", priv);
if (ret)
goto err_irq;
ret = register_device(priv);
if (ret)
goto err_register;
return 0;
err_register:
free_irq(priv->irq, priv);
err_irq:
/* devm_kzalloc 自动释放,无需手动 free */
err_alloc:
return ret;
}
5. 找到适合贡献的地方
drivers/staging
drivers/staging/ 目录包含尚未达到主线质量标准的驱动代码。这些驱动的 TODO 文件明确列出了需要改进的问题,是新手的最佳入口。
# 列出所有 staging 驱动及其 TODO 文件
ls drivers/staging/
cat drivers/staging/greybus/TODO
# 用 checkpatch 扫描某个 staging 驱动
scripts/checkpatch.pl --strict -f drivers/staging/greybus/*.c
# 搜索 coding style 问题(典型:用 printk 替代 dev_err)
grep -rn "printk(" drivers/staging/greybus/ | grep -v "KERN_"
使用 sparse 检查类型注解
# 安装 sparse
sudo apt-get install -y sparse
# 只编译目标驱动(不需要全量编译)
make C=1 drivers/staging/greybus/
# C=2 在所有文件上运行 sparse(包括已经编译过的)
make C=2 drivers/staging/greybus/
# 常见 sparse 警告:
# warning: incorrect type in argument
# warning: context imbalance in 'func' -- unexpected unlock
6. 准备第一个 Patch
理解问题
在修改任何代码之前,先彻底理解问题。用 git log --oneline drivers/xxx/ 查看该文件的修改历史,用 git blame 找到相关代码的原始作者,阅读相关的内核文档和代码注释。确认这确实是一个 bug 或规范违反,而不是你对内核 API 的误解。
# 创建工作分支(分支名描述具体改动,便于 maintainer 识别)
git checkout -b staging/greybus/fix-coding-style
# 编辑文件,做最小化、聚焦的改动
# 一个 patch 只做一件事!不要把多个无关改动混在一起
vim drivers/staging/greybus/core.c
# 只编译目标模块验证改动不引入编译错误
make -j$(nproc) drivers/staging/greybus/
# sparse 检查
make C=1 drivers/staging/greybus/
checkpatch.pl 检查
# 检查最新一个 commit(最常用方式)
scripts/checkpatch.pl --strict -g HEAD
# 检查一个 .patch 文件
scripts/checkpatch.pl --strict 0001-staging-greybus-fix-style.patch
# 检查某个源文件(不需要先 commit)
scripts/checkpatch.pl --strict -f drivers/staging/greybus/core.c
# 输出示例:
# WARNING: line over 100 characters
# ERROR: do not use assignment in if condition
# CHECK: 'foo' may be better written as 'bar'
# total: 0 errors, 1 warnings, 0 checks, 42 lines checked
目标: 发送前确保
total: 0 errors, 0 warnings。CHECK 级别的提示(蓝色)视情况处理,不是强制要求,但能消除最好全消。
撰写 commit message
git add drivers/staging/greybus/core.c
git commit
# commit message 格式(非常重要!):
# 第一行:subsystem: component: Short description (50字符以内)
# 空行
# 正文:解释为什么做这个改动(WHY),而不是做了什么(WHAT,代码已经说明)
# 空行(如有)
# 标签行(Signed-off-by 是强制要求)
staging: greybus: replace printk with dev_err for proper log context
Using raw printk() in device drivers loses the device context from
the log message, making it harder to correlate log entries with
specific devices in systems with multiple greybus instances.
Replace printk(KERN_ERR ...) calls with dev_err(dev, ...) which
automatically prepends the device name to the log output.
Signed-off-by: Your Name
Signed-off-by:
Signed-off-by是 Developer's Certificate of Origin (DCO) 的声明,表示你有权提交此代码。缺少此标签的 patch 会被直接拒绝。用git commit -s自动添加。
7. 找到正确的 Maintainer
Linux 内核有数百名 maintainer,每人负责特定子系统。发送 patch 前必须找到正确的 maintainer 和对应的邮件列表,否则 patch 会被忽略。
# get_maintainer.pl 根据文件路径或 patch 内容查找 maintainer
# 输出格式:Name (role: maintainer/reviewer/...)
# 对最新的 commit 生成 patch 并查找 maintainer
git format-patch -1 HEAD --stdout | scripts/get_maintainer.pl
# 或直接指定文件
scripts/get_maintainer.pl --file drivers/staging/greybus/core.c
# 示例输出:
# Greg Kroah-Hartman (maintainer:STAGING SUBSYSTEM)
# Johan Hovold (reviewer:GREYBUS SUBSYSTEM)
# [email protected] (open list:STAGING SUBSYSTEM)
# [email protected] (open list:GREYBUS SUBSYSTEM)
MAINTAINERS 文件是内核根目录下的维护者数据库,包含每个子系统的 M(maintainer)、R(reviewer)、L(mailing list)、S(status)、F(file pattern)等字段。理解这个文件可以帮助你判断 patch 应发给谁。
8. 发送 Patch
生成 .patch 文件
# 生成最新 1 个 commit 的 patch(-1 表示最近 1 个)
git format-patch -1 HEAD
# 生成多个 commit 的 patch 系列(自动添加序号前缀)
git format-patch -3 HEAD # 最近 3 个 commit
# 加 cover letter(补丁系列介绍,--cover-letter 生成 0000-cover-letter.patch)
git format-patch -3 HEAD --cover-letter
# 指定输出目录
git format-patch -1 HEAD -o /tmp/patches/
# 生成的文件示例:
# 0001-staging-greybus-replace-printk-with-dev_err.patch
配置 git send-email
# 安装 git send-email(可能需要单独安装)
sudo apt-get install -y git-email
# ~/.gitconfig 中配置 SMTP(以 Gmail 为例)
git config --global sendemail.smtpserver smtp.gmail.com
git config --global sendemail.smtpserverport 587
git config --global sendemail.smtpencryption tls
git config --global sendemail.smtpuser [email protected]
# SMTP 密码通过 git credential helper 管理或在命令行输入
# 以腾讯企业邮为例
git config --global sendemail.smtpserver smtp.exmail.qq.com
git config --global sendemail.smtpserverport 465
git config --global sendemail.smtpencryption ssl
发送 patch
# 先用 --dry-run 预览(不实际发送)
git send-email \
--to [email protected] \
--cc [email protected] \
--dry-run \
0001-staging-greybus-replace-printk-with-dev_err.patch
# 确认无误后去掉 --dry-run 发送
git send-email \
--to [email protected] \
--cc [email protected] \
0001-staging-greybus-replace-printk-with-dev_err.patch
# 发送 patch 系列(cover letter + 多个 patch)
git send-email \
--to [email protected] \
--cc [email protected] \
/tmp/patches/*.patch
重要: 内核 patch 必须以纯文本格式发送,不得使用 HTML 邮件,不得让邮件客户端重新换行。如果使用 Thunderbird,需安装 Markdown Here 插件或使用纯文本模式,并关闭 format=flowed。Gmail Web 界面会破坏 patch 格式,强烈建议使用
git send-email。
9. LKML 礼仪
Cover Letter 的写法
单个 patch 通常不需要 cover letter,但 patch 系列(超过 2 个 patch)必须附上 cover letter 解释整体意图。cover letter 的主题行格式为 [PATCH 0/N] Subject,正文说明为什么做这一系列改动、测试方法、已知限制。
回应 Review 意见
- 在收到意见的 48 小时内回复,表示你在关注
- 对每条意见逐一回应,明确表示接受、拒绝(并给出理由)或需要进一步讨论
- 修改后以
[PATCH v2]重新发送,在 cover letter 中列出变更日志 - 不要因为批评而沮丧——严格的审查是内核高质量的原因
常见标签含义
| 标签 | 含义 |
|---|---|
| Signed-off-by: | 作者声明有权提交此代码(DCO),也用于 maintainer 传递 patch |
| Reviewed-by: | 审阅者确认代码正确,推荐合入 |
| Acked-by: | 相关人员(如子系统 maintainer)表示不反对,但未深度审查 |
| Tested-by: | 测试者在真实硬件上验证了 patch 的功能 |
| Reported-by: | 记录 bug 报告者,感谢其贡献 |
| Fixes: | 指明本 patch 修复了哪个 commit 的 bug(格式:Fixes: abcdef ("commit subject")) |
用 patchwork 追踪 patch 状态
patchwork.kernel.org 自动收录发送到各邮件列表的 patch,并追踪其状态(New / Under Review / Accepted / Rejected / Deferred)。发送 patch 后,在 patchwork 搜索你的姓名或 patch 主题,确认 patch 已被正确收录。
10. Kernel Newbies 与学习资源
内核社区为新手提供了完善的学习和支持渠道:
- kernelnewbies.org — 内核新手最重要的门户,包含 TODO 列表(适合新手的任务)、每个内核版本的变更解读、IRC 频道等
- [email protected] — LKML 主列表,流量极大(每天数百封),建议用邮件过滤规则分类,或用 lore.kernel.org 网页存档阅读
- lore.kernel.org — 内核邮件列表的公开存档与搜索界面,支持线索化阅读
- #kernelnewbies (Libera.Chat IRC) — 实时问答频道,maintainer 和资深贡献者会在此解答问题
- 内核文档 —
Documentation/process/目录下有完整的贡献流程文档(submitting-patches.rst, email-clients.rst 等) - 每周内核状态追踪 — LWN.net 每周发布内核开发动态,是跟进内核社区的最佳渠道
行动清单: ① 克隆 linux-next,② 在 drivers/staging/ 找到一个 checkpatch 警告,③ 修复并用 checkpatch 验证,④ 用 get_maintainer.pl 找到收件人,⑤ 发送 patch 到邮件列表(即使是 staging 驱动也会有真人审查)。完成这五步,你就正式成为内核贡献者了。
上一章
← 第18章:迷你 Shell
下一章
第20章:综合实战 →