Linux内核开发流程指南 - 5. 编写正确的代码【ChatGPT】

发布时间 2023-12-08 20:14:56作者: 摩斯电码

5. 提交补丁

迟早会有一个时刻,你的工作准备好被提交给社区审查,并最终被合并到主线内核中。毫不奇怪,内核开发社区已经形成了一套用于提交补丁的惯例和程序,遵循这些规定将使所有相关人员的生活变得更加轻松。本文将试图以合理的细节涵盖这些期望;更多信息也可以在文件Documentation/process/submitting-patches.rst和Documentation/process/submit-checklist.rst中找到。

5.1. 何时提交

有一个持续的诱惑,就是在补丁完全“准备就绪”之前避免提交。对于简单的补丁,这并不是问题。然而,对于复杂的工作,通过在工作完成之前从社区获得反馈是非常有益的。因此,你应该考虑提交正在进行中的工作,甚至提供一个git树,以便感兴趣的开发人员随时了解你的工作进展。

在提交尚未准备好被合并的代码时,在提交本身中明确说明这一点是个好主意。还要提到任何尚未完成的主要工作和已知的问题。较少的人会查看已知为半成品的补丁,但那些查看的人会带着帮助你将工作引向正确方向的想法。

5.2. 创建补丁之前

在考虑向开发社区发送补丁之前,有一些事情应该做:

  • 尽可能测试代码。利用内核的调试工具,确保内核能够使用所有合理的配置选项构建,使用交叉编译器为不同的架构构建等。

  • 确保你的代码符合内核的编码风格指南。

  • 你的更改是否会影响性能?如果是,你应该运行基准测试,显示你的更改的影响(或好处);测试结果的摘要应该与补丁一起包含在内。

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

一般来说,在提交代码之前多花一些时间思考几乎总是会很快得到回报。

5.3. 补丁准备

为提交准备补丁可能需要相当多的工作,但再次尝试在这里节省时间通常并不明智,即使在短期内也是如此。

补丁必须针对特定版本的内核进行准备。一般来说,补丁应该基于Linus的git树中找到的当前主线。在基于主线时,应该从一个众所周知的发布点开始 - 一个稳定的或-rc发布 - 而不是在任意位置分支出主线。

可能需要针对-mm、linux-next或子系统树制作版本,以便进行更广泛的测试和审查。根据你的补丁所涉及的领域以及其他情况,基于这些其他树的补丁可能需要大量的工作来解决冲突和处理API更改。

只有最简单的更改应该格式化为单个补丁;其他一切都应该作为一系列逻辑更改。拆分补丁有点艺术;一些开发人员花费很长时间来弄清楚如何以社区期望的方式来做。然而,有一些经验法则可以极大地帮助:

  • 你提交的补丁系列几乎肯定不会是你的工作修订控制系统中找到的更改系列。相反,你所做的更改需要考虑其最终形式,然后以有意义的方式拆分开。开发人员对离散的、自包含的更改感兴趣,而不是你为实现这些更改所采取的路径。

  • 每个逻辑独立的更改应该格式化为一个单独的补丁。这些更改可以是小的(“向这个结构添加一个字段”)或大的(例如添加一个重要的新驱动程序),但它们应该在概念上是小的,并且可以用一行描述来说明。每个补丁应该做出一个特定的更改,可以单独审查并验证它是否做了它所说的事情。

  • 作为重新表述上面的指导原则的一种方式:不要在同一个补丁中混合不同类型的更改。如果一个单独的补丁修复了一个关键的安全漏洞,重新排列了一些结构,并重新格式化了代码,那么很可能它会被忽略,而重要的修复将会丢失。

  • 每个补丁应该产生一个能够正确构建和运行的内核;如果你的补丁系列在中途被中断,结果仍然应该是一个正常工作的内核。当使用“git bisect”工具查找回归时,部分应用补丁系列是一个常见的情况;如果结果是一个损坏的内核,你将使正在追踪问题的开发人员和用户的生活变得更加困难。

  • 但也不要过度。有一位开发人员曾经将对单个文件的一组编辑作为500个单独的补丁提交 - 这个行为并没有使他成为内核邮件列表上最受欢迎的人。一个单独的补丁可以相当大,只要它仍然包含一个单一的逻辑更改。

  • 添加一整套补丁的新基础设施可能是诱人的,但要避免在最后一个补丁启用整个基础设施之前将该基础设施保持未使用。如果该系列添加了回归,那么二分法将指出最后一个补丁是导致问题的原因,尽管真正的错误在其他地方。在可能的情况下,添加新代码的补丁应该立即使该代码生效。

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

5.4. 补丁格式和变更日志

现在你已经准备好了一系列完美的补丁用于发布,但工作还没有完全结束。每个补丁都需要被格式化成一条消息,以便迅速清晰地向世界传达其目的。为此,每个补丁将由以下内容组成:

  • 一个可选的“From”行,命名补丁的作者。如果你通过电子邮件转发别人的补丁,这一行是必需的,但如果有疑问,添加这一行也无妨。

  • 补丁作用的一行描述。这条消息应该足够让看到它但没有其他上下文的读者理解补丁的范围;这是将出现在“简短形式”变更日志中的行。这条消息通常以相关子系统名称开头,然后是补丁的目的。例如:

      gpio: 修复 CONFIG_GPIO_SYSFS=n 下的构建问题
    
  • 一个空行,后面是补丁内容的详细描述。这个描述可以尽可能长;它应该说明补丁的作用以及为什么应该将其应用到内核中。

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

上述内容一起构成了补丁的变更日志。编写良好的变更日志是至关重要但经常被忽视的艺术;值得再花一点时间讨论这个问题。在编写变更日志时,你应该记住将有许多不同的人会阅读你的文字。这些人包括子系统维护者和审阅者,他们需要决定是否应该包含这个补丁;发行人和其他维护者,他们试图决定是否应该将补丁回溯到其他内核版本;寻找问题原因的错误猎手,他们想知道这个补丁是否导致了他们正在追踪的问题;用户们想知道内核发生了什么变化,等等。一个好的变更日志以最直接和简洁的方式向所有这些人传达所需的信息。

为此,摘要行应该尽可能描述变更的影响和动机,考虑到一行的限制。然后详细描述可以扩展这些主题并提供任何需要的额外信息。如果补丁修复了一个 bug,如果可能的话引用引入该 bug 的提交(请提供提交的提交 ID 和标题)。如果问题与特定的日志或编译器输出相关联,包括该输出以帮助其他人寻找解决相同问题的方法。如果更改是为了支持后续补丁的其他更改,应该说明。如果内部 API 发生了变化,详细说明这些变化以及其他开发人员应该如何响应。总的来说,你越能站在所有阅读你变更日志的人的角度上,你的变更日志(以及整个内核)就会越好。

不用说,变更日志应该是提交更改到版本控制系统时使用的文本。它将被以下内容跟随:

  • 补丁本身,使用统一("-u")补丁格式。使用 "-p" 选项来进行 diff,将函数名称与更改关联起来,使得结果补丁更容易阅读。

你应该避免在补丁中包含对无关文件的更改(例如构建过程生成的文件,或者编辑器备份文件)。Documentation 目录中的 "dontdiff" 文件可以帮助你做到这一点;使用 "-X" 选项将其传递给 diff。

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

一个标签用于引用先前引入的问题的提交:

Fixes: 1f2e3d4c5b6a("提交的 SHA-1 ID 的前 12 个字符指定的提交的第一行")

另一个标签用于链接具有额外背景或详细信息的网页,例如导致补丁的早期讨论或由补丁实现的规范的文档:

Link: https://example.com/somewhere.html 可选的其他内容

许多维护者在应用补丁时也会添加此标签,以链接到补丁的最新公共审阅发布;通常这是由像 b4 这样的工具自动完成的,或者像 'Configuring Git' 中描述的 git 钩子。

如果 URL 指向由补丁修复的公共 bug 报告,使用 "Closes:" 标签代替:

Closes: https://example.com/issues/1234 可选的其他内容

一些 bug 跟踪器有能力在应用带有这种标签的提交时自动关闭问题。一些监视邮件列表的机器人也可以跟踪这样的标签并采取某些操作。私有的 bug 跟踪器和无效的 URL 是被禁止的。

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

tag: 全名 <电子邮件地址> 可选的其他内容

常用的标签包括:

  • Signed-off-by: 这是开发者确认他或她有权将补丁提交到内核中的证书。这是对开发者证书的同意,完整的文本可以在 Documentation/process/submitting-patches.rst 中找到。没有适当签名的代码不能合并到主线中。

  • 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:。

  • Cc: 指定的人收到了补丁的副本并有机会对其进行评论。

在添加标签到你的补丁时要小心,因为只有 Cc: 是适当的在未经明确允许的情况下添加;大多数情况下使用 Reported-by: 也是可以的,但如果 bug 是在私下报告的,请征得许可。

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: 补丁的一行描述

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

如果你有一系列重要的补丁,通常会发送一个作为第零部分的介绍描述。尽管这个约定并不是普遍遵循的;如果你使用它,请记住,介绍中的信息不会出现在内核的变更日志中。因此,请确保补丁本身具有完整的变更日志信息。

一般来说,多部分补丁的第二部分和后续部分应该作为第一部分的回复发送,以便它们在接收端能够正确地串联在一起。像 git 和 quilt 这样的工具有命令可以发送一组带有正确线索的补丁。如果你有一系列很长的补丁,并且使用的是 git,请避免使用 --chain-reply-to 选项,以避免创建异常深的嵌套。