如何进行 Linux 内核开发

这是关于此主题的终极文档。它包含如何成为 Linux 内核开发者以及如何学习与 Linux 内核开发社区合作的说明。它尽量不包含任何与内核编程技术方面相关的内容,但会帮助你指明正确的方向。

如果本文档中的任何内容过时,请将补丁发送给此文件的维护者,其信息列在文档底部。

引言

那么,你想学习如何成为一名 Linux 内核开发者吗?或者你的经理曾告诉你:“去为这个设备编写一个 Linux 驱动程序。”本文档的目标是通过描述你需要经历的过程以及如何与社区合作的提示,来教你实现这一目标所需的一切知识。它还将尝试解释社区为何如此运作的一些原因。

内核主要用 C 语言编写,部分架构相关的部分用汇编语言编写。内核开发需要对 C 语言有很好的理解。除非你计划为特定架构进行底层开发,否则不需要汇编语言(任何架构)。尽管以下书籍不能完全替代扎实的 C 语言教育和/或多年的经验,但它们作为参考书是很好的选择:

  • 《C 程序设计语言》(The C Programming Language),作者 Kernighan 和 Ritchie [Prentice Hall]

  • 《C 语言编程实践》(Practical C Programming),作者 Steve Oualline [O’Reilly]

  • 《C:参考手册》(C: A Reference Manual),作者 Harbison 和 Steele [Prentice Hall]

内核使用 GNU C 和 GNU 工具链编写。虽然它遵循 ISO C11 标准,但使用了一些标准中没有的扩展。内核是一个独立的 C 环境,不依赖标准 C 库,因此 C 标准的某些部分不受支持。不允许任意 long long 类型除法和浮点运算。有时很难理解内核对工具链及其所用扩展的假设,不幸的是,没有关于它们的权威参考资料。请查阅 gcc 的信息页面 (info gcc) 以获取相关信息。

请记住,你正在学习如何与现有的开发社区合作。这是一个由多元化人群组成的群体,对编码、风格和流程有着高标准。这些标准是随着时间的推移,基于他们发现最适合如此庞大且地理上分散的团队的工作方式而建立的。请尽量提前学习这些标准,因为它们有充分的文档记录;不要期望人们适应你或你公司的工作方式。

文档

Linux 内核源代码树包含大量文档,这些文档对于学习如何与内核社区交互非常有价值。当向内核添加新功能时,建议也添加新的文档文件来解释如何使用该功能。当内核更改导致内核暴露给用户空间的接口发生变化时,建议您将信息或补丁发送给手册页,向手册页维护者 alx@kernel.org 解释此更改,并抄送列表 linux-api@vger.kernel.org

以下是内核源代码树中需要阅读的文件列表:

Documentation/admin-guide/README.rst

此文件简要介绍了 Linux 内核的背景,并描述了配置和构建内核所需的步骤。内核新手应从此处开始。

Documentation/process/changes.rst

此文件列出了成功构建和运行内核所需的各种软件包的最低版本。

Documentation/process/coding-style.rst

这描述了 Linux 内核的编码风格,以及其背后的一些基本原理。所有新代码都应遵循此文档中的指南。大多数维护者只会在遵循这些规则的情况下接受补丁,并且许多人也只会在代码风格正确的情况下进行审查。

Documentation/process/submitting-patches.rst

此文件详细描述了如何成功创建和发送补丁,包括(但不限于):

  • 邮件内容

  • 邮件格式

  • 发送给谁

遵循这些规则不能保证成功(因为所有补丁都会受到内容和风格的严格审查),但不遵循它们几乎总是会阻碍成功。

其他关于如何正确创建补丁的优秀描述有:

《完美的补丁》(The Perfect Patch)

https://www.ozlabs.org/~akpm/stuff/tpp.txt

《Linux 内核补丁提交格式》(Linux kernel patch submission format)

https://web.archive.org/web/20180829112450/http://linux.yyz.us/patch-format.html

Documentation/process/stable-api-nonsense.rst

此文件描述了内核中刻意不提供稳定 API 的理由,包括以下几点:

  • 子系统垫片层(用于兼容性?)

  • 驱动程序在操作系统之间的可移植性。

  • 缓解内核源代码树内的快速变化(或阻止快速变化)

本文档对于理解 Linux 开发哲学至关重要,对于从其他操作系统开发转向 Linux 的人来说也非常重要。

Documentation/process/security-bugs.rst

如果您认为在 Linux 内核中发现了安全问题,请遵循本文档中的步骤通知内核开发者,并帮助解决问题。

Documentation/process/management-style.rst

本文档描述了 Linux 内核维护者的运作方式以及其方法论背后的共同精神。对于任何内核开发新手(或任何仅仅对此好奇的人)来说,这都是一篇重要的阅读材料,因为它解决了许多关于内核维护者独特行为的常见误解和困惑。

Documentation/process/stable-kernel-rules.rst

此文件描述了稳定版内核发布的规则,以及如果您想将更改纳入这些发布版本之一时该怎么做。

Documentation/process/kernel-docs.rst

一份与内核开发相关的外部文档列表。如果您未在内核文档中找到所需内容,请查阅此列表。

Documentation/process/applying-patches.rst

一篇很好的介绍,准确描述了补丁是什么以及如何将其应用于内核的不同开发分支。

内核还拥有大量可以从源代码本身或从 ReStructuredText 标记(ReST)自动生成的文档,就像本篇一样。这包括内核 API 的完整描述,以及如何正确处理锁机制的规则。

所有此类文档都可以通过在内核主源代码目录中分别运行以下命令生成为 PDF 或 HTML:

make pdfdocs
make htmldocs

make htmldocs

使用 ReST 标记的文档将在 Documentation/output 生成。它们也可以通过以下命令生成为 LaTeX 和 ePub 格式:

make latexdocs
make epubdocs

make latexpdfdocs

成为内核开发者

https://kernelnewbies.org

它有一个有用的邮件列表,您可以在其中提出几乎任何类型的基本内核开发问题(提问前请务必先搜索存档,以避免重复已回答的问题)。它还有一个 IRC 频道,您可以用来实时提问,以及大量有用的文档,这些文档对于学习 Linux 内核开发很有帮助。

该网站提供了关于代码组织、子系统和当前项目(包括树内和树外)的基本信息。它还描述了一些基本的后勤信息,例如如何编译内核和应用补丁。

https://kernelnewbies.org/KernelJanitors

这是一个很好的起点。它描述了 Linux 内核源代码树中需要清理和修复的一些相对简单的问题列表。通过与负责此项目的开发者合作,你将学习如何将补丁提交到 Linux 内核树的基本知识,如果你还没有想法,也可能会被指明接下来可以从事的方向。

https://elixir.bootlin.com/

开发流程

  • Linux 内核开发过程目前包含几个不同的主要内核“分支”以及许多不同的子系统特定内核分支。这些不同的分支是:

  • Linus 的主线树

  • 带有多个主版本号的各种稳定树

  • 子系统特定树

linux-next 集成测试树

主线树

  • 主线树由 Linus Torvalds 维护,可在 https://linuxkernel.org.cn 或存储库中找到。其开发流程如下:

  • 新内核发布后,将开放一个两周的窗口期,在此期间维护者可以向 Linus 提交大型差异补丁,通常是那些已经在 linux-next 中包含了几周的补丁。提交大型更改的首选方式是使用 git(内核的源代码管理工具,更多信息可在 https://git-scm.cn/ 找到),但普通补丁也完全可以接受。

  • 两周后,-rc1 内核发布,重点是让新内核尽可能地稳固。此时的大多数补丁都应修复回归问题。一直存在的错误不是回归问题,因此只有在重要的情况下才推动这类修复。请注意,一个全新的驱动程序(或文件系统)可能会在 -rc1 发布后被接受,因为只要更改是自包含的且不影响所添加代码之外的区域,就不会有引起回归的风险。在 -rc1 发布后,可以使用 git 向 Linus 发送补丁,但补丁也需要发送到公共邮件列表进行审查。

  • 每当 Linus 认为当前的 git 树处于合理且适合测试的状态时,就会发布一个新的 -rc 版本。目标是每周发布一个新的 -rc 内核。

此过程持续到内核被认为“准备就绪”,整个过程大约持续 6 周。

值得一提的是 Andrew Morton 在 linux-kernel 邮件列表中关于内核发布所写的内容:

“没有人知道内核何时发布,因为它根据已知的错误状态发布,而不是根据预设的时间表发布。”

带有多个主版本号的各种稳定树

版本号由三部分组成的内核是 -stable 内核。它们包含针对在特定主线版本中发现的安全问题或重大回归的相对较小且关键的修复。稳定系列中的每个版本都会增加版本号的第三部分,同时保持前两部分不变。

这是为那些想要最新稳定版内核且对帮助测试开发/实验版本不感兴趣的用户推荐的分支。

稳定树由“稳定”团队 <stable@vger.kernel.org> 维护,并根据需要发布。正常的发布周期约为两周,但在没有紧急问题时可能会更长。而安全相关问题则可能导致发布几乎立即进行。

内核树中的文件 Documentation/process/stable-kernel-rules.rst 记录了哪些类型的更改可以接受用于 -stable 树,以及发布过程是如何进行的。

子系统特定树

各个内核子系统的维护者——以及许多内核子系统开发者——都在源代码仓库中公开他们的当前开发状态。这样,其他人就可以看到内核不同领域正在发生的事情。在开发进展迅速的领域,开发者可能会被要求将他们的提交基于某个子系统内核树,以便避免提交与正在进行的其他工作之间的冲突。

这些仓库大多数是 git 树,但也使用其他 SCM(源代码管理系统),或者以 quilt 系列的形式发布补丁队列。这些子系统仓库的地址列在 MAINTAINERS 文件中。其中许多可以在 https://git.kernel.org/ 浏览。

在拟议的补丁被提交到子系统树之前,它会经过审查,审查主要发生在邮件列表上(请参见下面的相关部分)。对于一些内核子系统,此审查过程通过 patchwork 工具进行跟踪。Patchwork 提供了一个网页界面,显示补丁发布、对补丁的任何评论或修订,维护者可以将补丁标记为正在审查、已接受或已拒绝。这些 patchwork 站点大部分列在 https://patchwork.kernel.org/

linux-next 集成测试树

https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git

通过这种方式,linux-next 概述了下一个合并周期中预计会进入主线内核的内容。欢迎有冒险精神的测试人员对 linux-next 进行运行时测试。

错误报告

内核主源代码目录中的文件‘报告问题’描述了如何报告可能的内核错误,并详细说明了内核开发者追踪问题所需的信息。

管理错误报告

将你的 Hacking 技能付诸实践的最佳方法之一是修复他人报告的错误。这不仅有助于使内核更加稳定,你还将学会解决实际问题并提高技能,其他开发者也会注意到你的存在。修复错误是获得其他开发者认可的最佳方式之一,因为没有多少人喜欢浪费时间修复他人的错误。

要处理已报告的错误,请找到你感兴趣的子系统。查看 MAINTAINERS 文件,了解该子系统错误报告的接收方;通常是邮件列表,很少是错误追踪器。搜索该位置的存档以查找最近的报告,并在你认为合适的地方提供帮助。你可能还想查看 https://bugzilla.kernel.org 获取错误报告;只有少数内核子系统积极将其用于报告或跟踪,但整个内核的错误都会在那里归档。

邮件列表

https://subspace.kernel.org/subscribing.html

https://lore.kernel.org/linux-kernel/

强烈建议您在向列表发布您想提出的主题之前,先搜索存档。许多已详细讨论过的事项仅记录在邮件列表存档中。

大多数单独的内核子系统也有它们自己的独立邮件列表,用于进行开发工作。请参阅 MAINTAINERS 文件,获取这些列表用于不同组的清单。

https://subspace.kernel.org

https://subspace.kernel.org/etiquette.html

如果多人回复您的邮件,收件人抄送(CC:)列表可能会变得相当大。没有充分理由,请不要从抄送列表中删除任何人,也不要只回复列表地址。请习惯收到两封邮件,一封来自发件人,一封来自列表,并且不要尝试通过添加花哨的邮件头来调整,人们不会喜欢那样做。

请记住保持回复的上下文和归属完整,将“John Kernelhacker wrote ...:”等行保留在回复的顶部,并在引用的各个部分之间添加您的陈述,而不是在邮件顶部撰写。

如果您在邮件中添加补丁,请确保它们是纯文本格式且可读,如 Documentation/process/submitting-patches.rst 中所述。内核开发者不希望处理附件或压缩补丁;他们可能希望对您补丁的单行进行评论,只有这样才能实现。请确保您使用的邮件程序不会破坏空格和制表符。一个很好的初步测试是将邮件发送给自己,然后尝试自行应用您的补丁。如果不行,请修复您的邮件程序或更换它直到可以正常工作。

最重要的是,请记住尊重其他订阅者。

与社区合作

  • 内核社区的目标是提供最好的内核。当您提交补丁以供接受时,它将仅根据其技术优点进行审查。那么,您应该期待什么呢?

  • 批评

  • 评论

  • 变更请求

  • 解释说明请求

沉默

请记住,这是将您的补丁纳入内核的一部分。您必须能够接受对补丁的批评和评论,从技术层面评估它们,并重新制作您的补丁,或者提供清晰简洁的理由说明为什么不应进行这些更改。如果您的帖子没有回应,请等待几天再试一次,有时信息会在大量邮件中丢失。

  • 你不应该做什么?

  • 期望你的补丁被毫无疑问地接受

  • 变得防备

  • 忽略评论

在未进行任何请求的更改的情况下重新提交补丁

在一个寻求最佳技术解决方案的社区中,对于补丁的益处总是会有不同的意见。你必须乐于合作,并愿意调整你的想法以适应内核。或者至少愿意证明你的想法是值得的。请记住,犯错是可以接受的,只要你愿意努力走向正确的解决方案。

您的第一个补丁得到的回复可能只是一个列出您应该纠正的十几个问题的列表,这是正常的。这并意味着您的补丁不会被接受,也是针对您个人的。只需纠正针对您的补丁提出的所有问题并重新发送即可。

内核社区与企业结构之间的差异

内核社区的运作方式与大多数传统的企业开发环境不同。以下是您可以尝试做的一些事情,以避免问题:

  • 关于你提议的修改,可以说的好话

  • “这解决了多个问题。”

  • “这删除了 2000 行代码。”

  • “这是一个解释我试图描述内容的补丁。”

  • “我在 5 种不同的架构上测试过……”

  • “这是一系列小补丁,它们……”

“这提高了典型机器的性能……”

  • 你应该避免说的坏话

  • “我们在 AIX/ptx/Solaris 中就是这样做的,所以它一定很好……”

  • “我做这个已经 20 年了,所以……”

  • “这对于我们公司赚钱是必需的”

  • “这是为我们的企业产品线准备的。”

  • “这是我描述我想法的 1000 页设计文档”

  • “我为此工作了 6 个月……”

  • “这是一个 5000 行的补丁,它……”

  • “我重写了目前所有的混乱,成果在这里……”

“我有一个截止日期,这个补丁现在需要被应用。”

内核社区与大多数传统软件工程工作环境不同的另一个方面是交互的无面性。使用电子邮件和 IRC 作为主要沟通形式的一个好处是,不会因性别或种族而产生歧视。Linux 内核工作环境接受女性和少数族裔,因为你所呈现的只是一个电子邮件地址。国际化方面也有助于创造公平的竞争环境,因为你无法根据一个人的名字来猜测性别。一个男人可能叫 Andrea,一个女人可能叫 Pat。大多数在 Linux 内核中工作并表达过意见的女性都有积极的经历。

语言障碍可能会给一些不适应英语的人带来问题。为了在邮件列表中正确表达想法,可能需要良好掌握英语,因此建议您在发送电子邮件之前检查它们是否用英语表达清晰。

拆分你的修改

Linux 内核社区不乐于一次性接受大量代码。更改需要适当引入、讨论,并拆分为微小的独立部分。这与公司通常的做法几乎完全相反。您的提案也应在开发过程的早期引入,以便您可以收到关于您正在做的事情的反馈。这也让社区觉得您正在与他们合作,而不仅仅是将他们用作您功能的垃圾场。然而,不要一次性向邮件列表发送 50 封邮件,您的补丁系列在绝大多数情况下都应该比这小。

  1. 拆分修改的原因如下:

    小补丁增加了您的补丁被应用的可能性,因为它们不需要花费太多时间或精力来验证正确性。一个 5 行的补丁,维护者几乎不用多看一眼就能应用。然而,一个 500 行的补丁可能需要数小时来审查其正确性(所需时间与补丁大小呈指数级比例关系,或者类似的关系)。

  2. 小补丁还使得在出现问题时调试变得非常容易。逐个回滚补丁比在一个非常大的补丁应用后(并导致问题)进行剖析要容易得多。

不仅要发送小补丁,而且在提交之前重写和简化(或仅仅重新排序)补丁也很重要。

以下是内核开发者 Al Viro 的一个类比:

“想象一位老师批改数学学生的作业。老师不想看到学生在得出解决方案之前的尝试和错误。他们想看到最清晰、最优雅的答案。一个好学生知道这一点,绝不会在最终解决方案出来之前提交她的中间作品。

内核开发也是如此。维护者和审查者不想看到解决问题背后的思考过程。他们想看到一个简单而优雅的解决方案。”

在提出优雅的解决方案、与社区合作并讨论未完成的工作之间保持平衡可能具有挑战性。因此,尽早参与流程以获取反馈来改进您的工作是好的,但也要将您的更改保持在小块中,以便它们即使在您的整个任务尚未准备好包含时也可能被接受。

还要认识到,提交未完成的补丁并声称“稍后修复”是不可接受的。

证明你的修改的合理性

在拆分补丁的同时,让 Linux 社区知道为什么要添加这项更改也非常重要。新功能必须证明其必要性和实用性。

记录你的修改

  • 在发送补丁时,请特别注意您在电子邮件正文中撰写的内容。这些信息将成为补丁的变更日志信息,并永久保留供所有人查阅。它应该完整地描述补丁,包括:

  • 为什么需要此更改

  • 补丁的整体设计方法

  • 实现细节

测试结果

《完美的补丁》(The Perfect Patch)

https://www.ozlabs.org/~akpm/stuff/tpp.txt

有关所有这些内容的更多详细信息,请参阅文档的变更日志(ChangeLog)部分


所有这些事情有时都非常难以做到。可能需要数年才能完善这些实践(如果能完善的话)。这是一个持续改进的过程,需要大量的耐心和决心。但不要放弃,这是可能实现的。许多人以前都做到过,每个人都必须从你现在的位置开始。

感谢 Paolo Ciarrocchi 允许“开发流程”(https://lwn.net/Articles/94386/)部分基于他所撰写的文本,并感谢 Randy Dunlap 和 Gerrit Huizenga 提供了部分关于应该和不应该说什么的列表。同时感谢 Pat Mochel、Hanna Linder、Randy Dunlap、Kay Sievers、Vojtech Pavlik、Jan Kara、Josh Boyer、Kees Cook、Andrew Morton、Andi Kleen、Vadim Lobanov、Jesper Juhl、Adrian Bunk、Keri Harris、Frans Pop、David A. Wheeler、Junio Hamano、Michael Kerrisk 和 Alex Shepard 的审查、评论和贡献。没有他们的帮助,这份文档将无法完成。