5. 发布补丁

迟早,你的工作会准备好提交给社区进行审查,并最终被纳入主线内核。毫不奇怪,内核开发社区已经形成了一套用于发布补丁的约定和程序;遵循这些约定和程序将使所有相关人员的生活更加轻松。本文档将尝试合理详细地介绍这些期望;更多信息也可以在文件 Documentation/process/submitting-patches.rstDocumentation/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: fix build on CONFIG_GPIO_SYSFS=n
    
  • 一个空行,后跟对补丁内容的详细描述。此描述可以根据需要尽可能长;它应该说明补丁的功能以及为什么应将其应用于内核。

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

以上各项共同构成了补丁的变更日志。编写好的变更日志是一门至关重要但经常被忽视的艺术;值得花些时间来讨论这个问题。在编写变更日志时,你应该记住,许多不同的人会阅读你的文字。其中包括子系统维护人员和审阅者,他们需要决定是否应包含该补丁;发行商和其他维护人员,他们试图决定是否应将补丁向后移植到其他内核;漏洞猎手,他们想知道该补丁是否是他们正在追查的问题的原因;想知道内核如何变化的用户等等。好的变更日志以最直接和简洁的方式将所需的信息传递给所有这些人。

为此,摘要行应尽可能在单行限制内描述更改的效果和动机。详细描述可以扩展这些主题,并提供任何需要的附加信息。如果补丁修复了一个错误,请尽可能引用引入该错误的提交(并请在引用提交时提供提交 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

许多维护人员在应用补丁时也会添加此标签,以链接到该补丁的最新公开审查帖子;通常,这由 b4 等工具或 ‘配置 Git’ 中描述的 git hook 等工具自动完成。

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

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

某些错误跟踪器具有在应用带有此类标签的提交时自动关闭问题的能力。一些监控邮件列表的机器人也可以跟踪此类标签并采取某些操作。禁止使用私有错误跟踪器和无效 URL。

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

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

常用的标签是

  • 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: 标签也可以,但如果该错误是在私下报告的,请请求许可。

5.5. 发送补丁

在您发送补丁之前,您应该处理一些其他事项

  • 您是否确定您的邮件程序不会破坏补丁?邮件客户端执行了多余的空格更改或换行的补丁将无法在另一端应用,并且通常不会被详细检查。如果有任何疑问,请将补丁发送给自己,并确保它完好无损地显示出来。

    Documentation/process/email-clients.rst 提供了一些关于使特定邮件客户端能够发送补丁的有用提示。

  • 您是否确定您的补丁没有愚蠢的错误?您应该始终通过 scripts/checkpatch.pl 运行补丁,并解决它提出的问题。请记住,checkpatch.pl 虽然体现了许多关于内核补丁应该是什么样子的思考,但它并不比您更聪明。如果修复 checkpatch.pl 的问题会使代码变得更糟,请不要这样做。

补丁应始终以纯文本形式发送。请不要将它们作为附件发送;这使得审查者很难在他们的回复中引用补丁的各个部分。相反,只需将补丁直接放入您的消息中即可。

在发送补丁时,将副本发送给可能对此感兴趣的任何人非常重要。与其他一些项目不同,内核鼓励人们在发送过多副本方面犯错;不要假设相关人员会在邮件列表中看到您的帖子。特别是,副本应发送给

  • 受影响子系统的维护人员。如前所述,MAINTAINERS 文件是查找这些人员的首选位置。

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

  • 如果您正在回复错误报告或功能请求,请同时抄送原始发帖人。

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

  • 如果您正在修复错误,请考虑该修复是否应该进入下一个稳定更新。如果是这样,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 选项,以避免创建异常深的嵌套。