5. 提交补丁

总有一天,你的工作会准备好呈献给社区进行评审,并最终合并到主线内核中。不出所料,内核开发社区已经形成了一套用于提交补丁的常规和程序;遵循这些规范将使所有相关人员的工作轻松得多。本文将尝试以合理的详细程度涵盖这些期望;更多信息也可以在 Documentation/process/submitting-patches.rstDocumentation/process/submit-checklist.rst 文件中找到。

5.1. 何时提交

人们总是倾向于避免在补丁完全“准备好”之前发布它们。对于简单的补丁来说,这不是问题。然而,如果正在进行的工作很复杂,那么在工作完成之前从社区获得反馈会带来很多好处。因此,你应该考虑发布正在进行中的工作,甚至提供一个 git 树,以便感兴趣的开发人员可以随时了解你的工作进展。

在发布尚未准备好合并的代码时,最好在发布说明中明确说明这一点。同时提及任何尚未完成的主要工作以及任何已知的问题。虽然看半成品补丁的人会少一些,但那些看的人会带着可以帮助你将工作推向正确方向的想法而来。

5.2. 创建补丁之前

在考虑向开发社区发送补丁之前,应该完成许多事情。其中包括

  • 尽你所能对代码进行测试。利用内核的调试工具,确保内核能够在所有合理的配置选项组合下构建,使用交叉编译器为不同的架构进行构建,等等。添加测试,可能使用像 KUnit 这样的现有测试框架,并将它们作为你补丁集的一个独立成员包含在内(有关补丁集的更多信息,请参见下一节)。请注意,在影响某些子系统时,这可能是强制性的。例如,库函数(位于 lib/ 目录下)在几乎所有地方都广泛使用,因此期望对其进行适当的测试。

  • 确保你的代码符合内核编码风格规范。

  • 你的改动会带来性能影响吗?如果是这样,你应该运行基准测试来显示改动的影响(或益处);结果的摘要应包含在补丁中。

  • 确保你有权发布这些代码。如果这项工作是为雇主完成的,雇主可能拥有该工作的权利,并且必须同意在 GPL 下发布它。

通常情况下,在发布代码之前多加思考,几乎总能在短时间内得到回报。

5.3. 补丁准备

准备要发布的补丁可能需要大量的精力,但同样地,即使在短期内,试图在这里省时通常也是不可取的。

补丁必须基于特定版本的内核进行准备。通常情况下,补丁应基于 Linus 的 git 树中的当前主线。在基于主线时,应从一个广为人知的发布点(如稳定版或 -rc 版本)开始,而不是在主线的任意位置分叉。

不过,为了便于进行更广泛的测试和评审,可能需要针对 -mm、linux-next 或子系统树制作版本。根据你的补丁所涉及的领域以及其他地方的进展,基于这些其他树创建补丁可能需要大量的精力来解决冲突和处理 API 变更。

只有最简单的改动才可以格式化为单个补丁;其他所有改动都应制作为一系列符合逻辑的改动。拆分补丁有点像一门艺术;一些开发人员花很长时间来摸索如何以社区期望的方式进行拆分。然而,有一些经验法则可以提供很大帮助

  • 你发布的补丁集几乎肯定不是你的工作版本控制系统中的修改序列。相反,你需要将你所做的修改视为最终形态,然后以合理的方式将它们拆分。开发人员对离散的、自包含的修改感兴趣,而不是你达到这些修改所走过的路径。

  • 每个逻辑上独立的修改都应格式化为一个单独的补丁。这些修改可以很小(“为此结构体添加一个字段”)或很大(例如添加一个重要的新驱动程序),但在概念上它们应该足够小,且适合用单行描述。每个补丁都应该进行一项具体的修改,该修改可以独立进行评审,并验证其是否实现了它所宣称的功能。

  • 换一种方式重申上述准则:不要在同一个补丁中混合不同类型的修改。如果一个补丁既修复了严重的安全漏洞,又重新排列了一些结构体,还重构了代码,那么它很有可能会被忽略,从而导致重要的修复丢失。

  • 每个补丁都应该生成一个能够正确构建和运行的内核;如果你的补丁集在中间中断应用,其结果仍然应该是一个可工作的内核。在使用 “git bisect” 工具查找回归(regression)时,部分应用补丁集是一种常见场景;如果结果是一个损坏的内核,你将给那些从事追踪问题这一崇高工作的开发人员和用户带来更多麻烦。

  • 不过,也不要矫枉过正。曾有一位开发人员将对单个文件的修改发布为 500 个独立的补丁——这一举动并没有让他成为内核邮件列表上最受欢迎的人。只要单个补丁仍然包含单一的 逻辑 修改,它可以有相当的规模。

  • 人们很容易受到诱惑,通过一系列补丁添加全新的基础结构,但在该系列的最后一个补丁启用整个功能之前,让该基础结构保持闲置状态。如果可能的话,应避免这种诱惑;如果该系列补丁引入了回归,二分查找(bisection)会将最后一个补丁指认为导致问题的原因,尽管真正的 bug 在别处。只要有可能,添加新代码的补丁就应该立即激活该代码。

在“真正的工作”完成后,努力创建完美的补丁集可能是一个令人沮丧的过程,需要花费相当多的时间和思考。然而,如果做得好,这些时间是非常值得的。

5.4. 补丁格式与变更日志

现在你已经有了一组完美的待发布补丁,但工作还没有完全结束。每个补丁都需要格式化为一条消息,能够快速、清晰地向其他人传达其目的。为此,每个补丁将由以下部分组成

  • 一个可选的 “From” 行,指明补丁的作者。只有当你通过电子邮件转发别人的补丁时,这一行才是必需的,但在不确定时加上它也总没有坏处。

  • 对补丁作用的单行描述。对于没有任何其他上下文就能看到它的读者来说,这条消息应该足以弄清楚补丁的范围;它就是将出现在“简短形式”变更日志中的那一行。此消息通常的格式是:首先是相关的子系统名称,后跟补丁的用途。例如

    gpio: fix build on CONFIG_GPIO_SYSFS=n
    
  • 一个空行,后面跟着对补丁内容的详细描述。该描述可以根据需要写得很长;它应该说明补丁做了什么以及为什么应该将它应用到内核中。

  • 一个或多个标签行,其中至少包含一行来自补丁作者的 Signed-off-by:。标签将在下文进行更详细的描述。

以上各项共同构成了补丁的变更日志。编写好的变更日志是一门至关重要但经常被忽视的艺术;花点时间再讨论一下这个问题是值得的。在编写变更日志时,你应该记住会有许多不同的人阅读你写的内容。这些人包括需要决定是否应包含该补丁的子系统维护者和评审者,试图决定是否应将补丁向后移植(backport)到其他内核的发行版维护人员和其他维护人员,想知道该补丁是否是他们正在追踪的问题的罪魁祸首的排错人员,以及想要了解内核发生了哪些变化的用户等。一份好的变更日志以最直接、最简洁的方式向所有这些人传达所需的信息。

为此,在单行约束的条件下,摘要行应尽可能好地描述更改的影响和动机。然后,详细描述可以对这些主题进行阐述,并提供任何所需的附加信息。如果补丁修复了一个 bug,请尽可能引用引入该 bug 的提交(在引用提交时,请同时提供提交 ID 和标题)。如果某个问题与特定的日志或编译器输出相关,请包含该输出以帮助其他人寻找同一问题的解决方案。如果此更改旨在支持后续补丁中的其他更改,请说明这一点。如果内部 API 发生了更改,请详细说明这些更改以及其他开发人员应如何应对。总的来说,你越能设身处地为每一个将要阅读你的变更日志的人着想,该变更日志(以及整个内核)就会越好。

不用说,变更日志应该是在将更改提交到版本控制系统时使用的文本。其后将是

  • 补丁本身,采用统一的(“-u”)补丁格式。在 diff 中使用 “-p” 选项会将函数名与修改关联起来,从而使生成的补丁更易于其他人阅读。

前面已经简要提到的标签用于提供有关补丁是如何产生的深入见解。它们在 Documentation/process/submitting-patches.rst 文档中有详细描述;以下是简要总结。

一个标签用于引用引入了该补丁所修复问题的早期提交

Fixes: 1f2e3d4c5b6a ("The first line of the commit specified by the first 12 characters of its SHA-1 ID")

另一个标签用于链接包含更多背景信息或细节的网页,例如促成该补丁的早期讨论,或者包含该补丁所实现的规范的文档

Link: https://example.com/somewhere.html  optional-other-stuff

根据首席企鹅(Chief Penguin)的指导意见,只有当 Link: 标签能指向提交本身中找不到的有价值信息时,才应将其添加到提交中。

如果 URL 指向由该补丁修复的公开 bug 报告,请改用 “Closes:” 标签

Closes: https://example.com/issues/1234  optional-other-stuff

当应用带有此类标签的提交时,一些 bug 跟踪系统能够自动关闭问题。监控邮件列表的一些机器人也可以追踪此类标签并采取特定动作。禁止使用私有 bug 跟踪系统和无效的 URL。

另一种标签用于记录谁参与了补丁的开发。这些标签中的每一个都使用以下格式

tag: Full Name <email address>  optional-other-stuff

常用的标签有

  • Signed-off-by: 这是开发者的证明,表明他或她有权提交该补丁以将其合并到内核中。这是对开发者起源证书(Developer’s Certificate of Origin)的协议,其全文可在 Documentation/process/submitting-patches.rst 中找到。没有正确签名(signoff)的代码不能合并到主线中。

  • Co-developed-by: 表明该补丁由多位开发者共同创建;当多个人共同完成一个补丁时,它用于给予共同作者署名(除了由 From: 标签署名的作者之外)。每一个 Co-developed-by: 后面必须紧跟关联共同作者的 Signed-off-by:。详细信息和示例可以在 Documentation/process/submitting-patches.rst 中找到。

  • Acked-by: 表明另一位开发者(通常是相关代码的维护者)同意该补丁适合被合并到内核中。

  • Tested-by: 表明具名人员已经测试了该补丁并发现其工作正常。

  • Reviewed-by: 具名开发者已对补丁的正确性进行了评审;有关更多详细信息,请参阅 Documentation/process/submitting-patches.rst 中的评审员声明。

  • Reported-by: 指出报告了由该补丁修复的问题的用户;此标签用于表彰那些测试我们的代码并在事情运行不正常时通知我们的(通常未受到足够赞赏的人)。注意,此标签后面应跟一个指向该报告的 Closes: 标签,除非该报告在网上无法获取。如果补丁修复了所报告的部分问题,可以使用 Link: 标签代替 Closes:。

  • Suggested-by: 标签表明补丁的想法是由具名人员提出的,并确保将该想法的功劳归于此人。希望这能激励他们在未来再次帮助我们。

  • Cc: 具名人员收到了一份补丁的副本,并有机会对其发表评论。

在你的补丁中添加上述标签时要小心,因为除了 Cc:、Reported-by: 和 Suggested-by: 之外的所有标签都需要被命名人员的明确许可。对于这三个标签,如果根据 lore 存档或提交历史,此人曾使用该姓名和电子邮件地址对 Linux kernel 做过贡献——并且在 Reported-by: 和 Suggested-by: 的情况下,是以公开方式进行报告或提出建议的,那么隐式许可就足够了。注意,从这个意义上说,bugzilla.kernel.org 是一个公共场所,但那里使用的电子邮件地址是私密的;因此不要在标签中公开它们,除非此人在先前的贡献中曾使用过它们。

5.5. 发送补丁

在发送补丁邮件之前,还有几件事你需要注意

  • 你确定你的邮件客户端不会损坏补丁吗?经历了邮件客户端进行的无端空白字符更改或自动换行的补丁将无法在接收端应用,并且通常不会受到详细的检查。如果有任何疑问,请将补丁发给自己,并确认它显示时是完好无损的。

    Documentation/process/email-clients.rst 提供了一些有用的提示,教你如何配置特定的邮件客户端来发送补丁。

  • 你确定你的补丁没有低级错误吗?你应当始终通过 scripts/checkpatch.pl 运行补丁,并处理它提出的抱怨。请记住,checkpatch.pl 尽管凝聚了关于内核补丁应该是什么样子的相当多的思考,但它并不比你更聪明。如果修复 checkpatch.pl 的抱怨会使代码变糟,那就不要去修复它。

补丁应始终以纯文本形式发送。请不要将它们作为附件发送;这会使评审人员在回复中引用补丁的某些部分变得更加困难。相反,只需将补丁直接放入你的邮件正文中。

在通过邮件发送补丁时,将副本发送给任何可能感兴趣的人是很重要的。与某些其他项目不同,内核鼓励人们宁可多发送几份副本;不要假设相关人员会在邮件列表上看到你的发布。特别是,副本应发送给

  • 受影响子系统的维护者。如前所述,MAINTAINERS 文件是寻找这些人员的第一个地方。

  • 一直在同一领域工作的其他开发人员——尤其是那些现在可能正在那里工作的人。使用 git 还有谁修改了你正在处理的文件会很有帮助。

  • 如果你是在回应一个 bug 报告或功能请求,也请抄送原作者。

  • 向相关的邮件列表发送一份副本,或者如果没有其他适用的列表,则发送到 linux-kernel 列表。

  • 如果你正在修复一个 bug,请考虑该修复是否应该进入下一个稳定版更新。如果是这样,stable@vger.kernel.org 应当获得一份补丁副本。同时,在补丁本身的标签中添加一个 “Cc: stable@vger.kernel.org”;这会使得当你的修复进入主线时,稳定版团队能收到通知。

在为补丁选择收件人时,最好心里有个底,知道你认为谁最终会接受该补丁并将其合并。虽然可以直接将补丁发送给 Linus Torvalds 并让他合并,但通常不这样做。Linus 很忙,而且有子系统维护者负责监督内核的特定部分。通常你会希望该维护者来合并你的补丁。如果没有明显的维护者,Andrew Morton 通常是最后求助的补丁目标。

补丁需要好的主题行。补丁主题行的典型格式类似于

[PATCH nn/mm] subsys: one-line description of the patch

其中 “nn” 是补丁的序号,“mm” 是系列中补丁的总数,“subsys” 是受影响子系统的名称。显然,对于单个、独立的补丁,可以省略 nn/mm。

如果你有一系列相当大的补丁,习惯上将介绍性描述作为第零部分发送。不过,这个惯例并未被普遍遵循;如果你使用了它,请记住介绍中的信息不会进入内核变更日志。因此,请确保补丁本身包含完整的变更日志信息。

一般来说,多部分补丁的第二部分及后续部分应该作为对第一部分的回复发送,以便它们在接收端全部按线程组织在一起。像 git 和 quilt 这样的工具拥有以正确的线程结构发送一组补丁的命令。但是,如果你有一个很长的补丁集,并且正在使用 git,请避免使用 --chain-reply-to 选项,以避免创建异常深层的嵌套。