Skip to content

第二百零一章:补丁之道

飞升期

涉及内核源码:

林小源站在内核贡献殿的门前,殿门由无数细密的代码丝线编织而成,每一根丝线都是一段修改的记录。门楣上镌刻着三个古朴的大字——"补丁殿"。

他推门而入,殿内空旷,四壁悬挂着无数闪烁的光幕,每一块光幕都展示着一封邮件的模样。一位白发苍苍的老者端坐在殿中央的石台上,手中捧着一封散发着金光的信笺。

"晚辈林小源,前来学习补丁之道。"林小源拱手行礼。

老者抬起眼皮,目光如电:"你可知什么是补丁?"

"补丁是……向内核提交修改的方式?"林小源试探着回答。

老者将手中的信笺轻轻抛出,信笺在空中展开,露出其中的内容——一段代码修改,附着详细的说明。"补丁不是代码本身,而是一封邮件。它承载着你的修改、你的理由、你的承诺。"

林小源伸手触碰那封信笺,指尖传来一阵温热。他感受到补丁中蕴含的信息:Subject 行概括了修改的目的,正文详细描述了修改的原因,Signed-off-by 则是提交者的签名。

"提交补丁的流程,"老者缓缓说道,"首先要获取最新的代码树,从主线拉出属于你自己的分支。在分支上修改、测试、提交,然后用 git format-patch 生成补丁,最后通过 git send-email 发送到邮件列表。"

老者顿了顿,语气变得严厉:"但最重要的是——一个补丁,只做一件事。"

"还有一条,"老者又补了一句,"补丁要能自己说清楚自己。"

他抬手一招,殿壁上落下一封旧邮件。邮件开头只有一句"fix bug",下面便是大段 diff。老者没有看代码,只把邮件递给林小源:"十年后,有人用 git log 查到这里。他不知道你当年在邮件列表里争过什么,不知道哪台机器崩过,不知道哪个用户报过错。他只看到这封 commit log。你让他怎么判断这段代码为什么存在?"

林小源沉默。

"所以,"老者说,"先写问题,再写影响,再写改法。崩溃、锁死、性能回退、延迟尖峰、dmesg 片段、复现条件,能量化就量化。若是修复旧提交,用 Fixes: 指明至少十二位提交号和标题;若是回应公开报告,用 Closes: 指向报告;若是承接讨论,用 Link: 指向 lore 上的来路。补丁不是只给今天的维护者看,也是给多年后的排错者看。"

c
/*
 * 补丁提交流程:
 *
 * 1. 获取最新代码
 *    git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
 *
 * 2. 创建分支
 *    git checkout -b my-feature
 *
 * 3. 修改代码
 *    遵循编码规范
 *
 * 4. 提交
 *    git add -u
 *    git commit -s
 *
 * 5. 生成补丁
 *    git format-patch -1
 *
 * 6. 发送补丁
 *    git send-email --to=maintainer@kernel.org patch.mbox
 *
 * 补丁格式:
 *   Subject: [PATCH] 子系统: 简短描述
 *
 *   详细描述
 *
 *   Signed-off-by: Name <email>
 *
 * 注意:
 *   一个补丁只做一件事
 *   补丁要能编译通过
 *   补丁要能通过测试
 */

/* 模拟补丁 */
struct patch {
    char subject[128];
    char description[256];
    char author[64];
    char signed_off_by[64];
    int lines_changed;
};

void print_patch(struct patch *p) {
    printf("Subject: %s\n\n", p->subject);
    printf("%s\n\n", p->description);
    printf("Signed-off-by: %s\n", p->signed_off_by);
    printf("---\n");
    printf("Lines changed: %d\n", p->lines_changed);
}

printf("=== 补丁之道 — 向内核贡献 ===\n\n");

printf("补丁是向内核提交修改的方式:\n\n");

/* 示例补丁 */
struct patch example = {
    .subject = "[PATCH] ext4: fix memory leak in ext4_fill_super",
    .description = "Fix a memory leak in ext4_fill_super() where\n"
                   "the super block buffer head was not released\n"
                   "on error path.\n\n"
                   "Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>",
    .author = "Linus Torvalds",
    .signed_off_by = "Linus Torvalds <torvalds@linux-foundation.org>",
    .lines_changed = 5
};

printf("--- 示例补丁 ---\n");
print_patch(&example);

printf("\n--- 提交流程 ---\n");
printf("1. 获取最新代码:\n");
printf("   git clone linux.git\n\n");
printf("2. 创建分支:\n");
printf("   git checkout -b my-feature\n\n");
printf("3. 修改代码:\n");
printf("   遵循编码规范\n\n");
printf("4. 提交:\n");
printf("   git commit -s\n\n");
printf("5. 生成补丁:\n");
printf("   git format-patch -1\n\n");
printf("6. 发送补丁:\n");
printf("   git send-email\n\n");

printf("--- 补丁原则 ---\n");
printf("1. 一个补丁只做一件事\n");
printf("   简单、清晰\n\n");
printf("2. 补丁要能编译通过\n");
printf("   不能引入编译错误\n\n");
printf("3. 补丁要能通过测试\n");
printf("   不能引入回归\n\n");
printf("4. 补丁要有好的描述\n");
printf("   说明为什么修改\n\n");

printf("5. 描述要能自包含\n");
printf("   问题、影响、技术做法、测试结果\n\n");

printf("6. 常见标签\n");
printf("   Fixes: 指向引入 bug 的提交\n");
printf("   Closes: 指向被修复的公开报告\n");
printf("   Link: 指向相关讨论或背景\n");

printf("--- Signed-off-by ---\n");
printf("含义:\n");
printf("  我写了这个补丁\n");
printf("  我有权提交这个补丁\n");
printf("  这个补丁符合 DCO\n\n");
printf("DCO (Developer Certificate of Origin):\n");
printf("  开发者来源证书\n");
破关试炼

补丁初试

补丁描述要让多年后的读者也能理解,因此必须说明问题、影响和哪一类技术内容?

答对后才能继续滑动和进入下一章。

林小源盯着殿壁上的一块光幕,上面展示着一个失败的补丁案例——一个补丁同时修改了网络驱动和文件系统代码,被维护者直接打了回来。

"为什么?"他不解地问。

老者从石台上走下来,枯瘦的手指点了点那块光幕:"你看,这个补丁改了两个毫不相关的子系统。审查者要找网络专家和文件系统专家同时来看,任何一个发现问题,整个补丁都要重做。回滚的时候,你想回滚网络的修改,却连文件系统的也一起回滚了。"

林小源若有所悟。老者又指向另一块光幕,上面是一个简洁的补丁——只有五行改动,修复了一个具体的内存泄漏。

"这才是好补丁,"老者的语气柔和了些许,"一个补丁只做一件事:修一个 bug,加一个功能,或做一个重构。审查者一眼就能看明白,测试者知道该测什么,维护者知道该不该合并。简单,才清晰。"

林小源默默记下:补丁的力量不在于修改了多少行代码,而在于每一次修改都清晰可辨。

老者却没有让他停下。他又展开一组补丁系列,十几封信笺按顺序悬在空中。

"系列里的每一封,也都必须能独立站住。"老者说,"如果第七封补丁让内核无法编译,git bisect 追查回归时就会把后来所有人拖进泥潭。一个好系列,不是把你修行时的草稿照搬出来,而是把最终结果重新切成可审查、可回滚、可二分的逻辑步骤。"

林小源望着那串信笺,终于明白"一事一补丁"不是形式主义。它保护的是审查者的时间,是维护者的判断,也是未来调试者沿着历史回溯时脚下的路。

破关试炼

拆分之试

为了让 git bisect 能在系列中任意停下,每个补丁应用后内核至少应保持什么状态?

答对后才能继续滑动和进入下一章。

老者回到石台上,从袖中取出一枚印章,印章底部刻着复杂的符文。

"Signed-off-by,"老者将印章举起,"这不是随便盖上去的。"

林小源凑近细看,感受到印章上散发出的庄严气息。

"当你在补丁上签下 Signed-off-by,你是在做三件事,"老者的声音低沉而郑重,"第一,你声明这个补丁是你写的,或者你有权提交它。第二,你声明这个补丁符合 DCO——开发者来源证书。第三,你将自己的名字刻入了内核的历史。"

老者将印章放在石台上,发出沉闷的声响。"这不是形式,是法律承诺。每一个签名都追溯到具体的人,每一段代码都有明确的归属。内核的信任体系,就建立在这些签名之上。"

林小源郑重地接过那枚印章,感受到它的重量——不仅是物理的重量,更是责任的重量。

"还有别人的印章,也不能乱拿。"老者收起威严,语气反而更谨慎,"Reviewed-by、Tested-by、Acked-by 不是装饰。除少数标签外,把别人的名字放进补丁,通常需要对方明确给过。若补丁改动很大,旧的 Reviewed-by 也可能不再适用。信用是链条,链条上的每一环都要真实。"

林小源忽然想起前几卷那些看似不起眼的引用计数。少加一次,会泄漏;多减一次,会悬空。补丁上的标签也是这样,少了,历史不完整;多了,信任就被污染。

破关试炼

签名之试

补丁中声明开发者有权提交并符合开发者来源证书的标签是什么?

答对后才能继续滑动和进入下一章。

道藏笔记

内核启示

补丁是向内核提交修改的方式。

提交流程:

  • 获取最新代码
  • 创建分支
  • 修改代码
  • 提交 (git commit -s)
  • 生成补丁 (git format-patch)
  • 发送补丁 (git send-email)

补丁原则:

  • 一个补丁只做一件事
  • 能编译通过
  • 能通过测试
  • 有好的描述
  • 每个补丁应用后都不应破坏二分
  • 描述要自包含,说明问题、影响和技术做法

Signed-off-by:

  • 声明作者身份
  • 声明有权提交
  • 符合 DCO

常见追踪标签:

  • Fixes: — 指向引入 bug 的提交
  • Closes: — 指向被修复的公开报告
  • Link: — 指向相关公开讨论

补丁是贡献——向内核社区提交修改。


破关试炼

道藏复核

当补丁修复某个旧提交引入的 bug 时,应使用哪一个标签指向那个提交?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道