第 19 章

内核贡献

第19章:Linux 内核开发与社区贡献

向 Linux 内核提交补丁是许多工程师的技术里程碑。内核社区规模庞大、流程严格,但同时也对新手友好——只要你遵循既定规范。本章覆盖从获取源码、编译内核、理解编码规范,到用 checkpatch.pl 检查代码、用 git format-patch 生成补丁、通过 git send-email 发送至 LKML,直至获得 maintainer 认可的完整流程。

1. 内核开发入门:门槛与机会

内核开发与应用开发的本质区别在于运行环境:内核代码在没有标准库、没有内存保护、没有浮点运算的特权模式下运行。一个空指针解引用不会产生 segfault,而是直接触发内核崩溃(panic)。这要求开发者对硬件和操作系统原理有深入理解。

尽管门槛不低,内核社区对新手友好的贡献类型有很多。新手不需要从编写新驱动或实现新子系统开始,以下是推荐的入门路径:

回报: 成为内核贡献者意味着你的代码运行在全球数十亿台设备上。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;

命名规范

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 意见

常见标签含义

标签 含义
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 与学习资源

内核社区为新手提供了完善的学习和支持渠道:

行动清单: ① 克隆 linux-next,② 在 drivers/staging/ 找到一个 checkpatch 警告,③ 修复并用 checkpatch 验证,④ 用 get_maintainer.pl 找到收件人,⑤ 发送 patch 到邮件列表(即使是 staging 驱动也会有真人审查)。完成这五步,你就正式成为内核贡献者了。

  上一章
  ← 第18章:迷你 Shell


  下一章
  第20章:综合实战 →
本章评分
4.6  / 5  (10 评分)

💬 留言讨论