Linux内核开发流程
Linux内核在20世纪90年代初期是一个相当松散的事务,涉及的用户和开发者数量相对较少。随着用户基数达到数百万,并有约2000名开发者在一年内参与开发,内核不得不演变出一系列流程以确保开发顺利进行。要成为其中有效的一部分,就需要对这个流程的运作有一个扎实的理解。
2.1. 大局观
内核开发者采用了一个基于时间的宽松发布流程,每两到三个月发布一个新的主要内核版本。最近的发布历史如下:
- 5.0
- 2019年3月3日
- 5.1
- 2019年5月5日
- 5.2
- 2019年7月7日
- 5.3
- 2019年9月15日
- 5.4
- 2019年11月24日
- 5.5
- 2020年1月6日
每个5.x版本都是一个带有新功能、内部API变化等的主要内核版本。一个典型的发布可能包含大约13000个变更集,涉及数十万行代码的更改。5.x是Linux内核开发的前沿;内核采用了一个不断集成重大变更的滚动式开发模型。
对于每个发布,相对简单的纪律性被遵循以合并补丁。在每个开发周期的开始,“合并窗口”被认为是打开的。在此期间,被认为足够稳定(并且被开发社区接受)的代码将被合并到主线内核中。新开发周期的大部分变更(以及所有重大变更)将在此期间合并,合并速度接近每天约1000个变更(“补丁”或“变更集”)。
合并窗口持续大约两周。在此期间结束时,Linus Torvalds将宣布窗口已关闭,并发布第一个“rc”内核。例如,对于即将成为5.6的内核,合并窗口结束时发布的将被称为5.6-rc1。-rc1发布是合并新功能的时间已经过去,开始稳定下一个内核的信号。
在接下来的六到十周内,只能提交修复问题的补丁到主线。偶尔会允许更重大的变更,但这种情况很少见;试图在合并窗口之外合并新功能的开发者往往会受到不友好的对待。一般规则是,如果错过了给定功能的合并窗口,最好的做法是等待下一个开发周期。(偶尔会为以前不受支持的硬件添加驱动程序做出例外;如果它们不涉及内核树中的代码,它们不会引起退化,应该可以随时添加)。
随着修复逐渐进入主线,补丁速率会随时间减慢。Linus大约每周发布一个新的-rc内核;一个正常的系列将在-rc6和-rc9之间达到足够稳定的程度,然后进行最终发布。在那时整个流程重新开始。
例如,以下是5.4开发周期的情况(所有日期为2019年):
- 9月15日
- 5.3稳定发布
- 9月30日
- 5.4-rc1,合并窗口关闭
- 10月6日
- 5.4-rc2
- 10月13日
- 5.4-rc3
- 10月20日
- 5.4-rc4
- 10月27日
- 5.4-rc5
- 11月3日
- 5.4-rc6
- 11月10日
- 5.4-rc7
- 11月17日
- 5.4-rc8
- 11月24日
- 5.4稳定发布
开发者如何决定何时关闭开发周期并创建稳定发布?最重要的指标是与以前版本的退化列表。没有欢迎的错误,但那些破坏过去系统的错误被认为是特别严重的。因此,引起退化的补丁被视为不受欢迎,并很可能在稳定期间被撤销。
开发者的目标是在进行稳定发布之前修复所有已知的退化。在现实世界中,要达到这种完美是很困难的;在这样规模的项目中有太多的变量。有一个点,延迟最终发布只会使问题变得更糟;等待下一个合并窗口的变更堆积会变得更大,导致下一次更多的退化。因此,大多数5.x内核发布时都会有一些已知的退化,但希望没有一个是严重的。
一旦稳定发布完成,其持续维护将交给“稳定团队”,目前是Greg Kroah-Hartman。稳定团队将使用5.x.y编号方案不时发布稳定发布的更新。要考虑进行更新发布,补丁必须(1)修复重大错误,并且(2)已经合并到下一个开发内核的主线。内核通常会在初始发布后的一个开发周期多一点时间接收稳定更新。因此,例如,5.2内核的历史如下(所有日期为2019年):
- 7月7日
- 5.2稳定发布
- 7月14日
- 5.2.1
- 7月21日
- 5.2.2
- 7月26日
- 5.2.3
- 7月28日
- 5.2.4
- 7月31日
- 5.2.5
- ...
- ...
- 10月11日
- 5.2.21
5.2.21是5.2发布的最终稳定更新。
一些内核被指定为“长期”内核;它们将获得更长时间的支持。请参考以下链接查看活跃的长期内核版本及其维护者:
https://www.kernel.org/category/releases.html
选择内核进行长期支持纯粹是维护者有需要和时间来维护该发布。目前没有已知的任何特定即将发布的版本的长期支持计划。
2.2. 补丁的生命周期
补丁并不会直接从开发者的键盘进入主线内核。相反,有一个有点复杂(但有点非正式)的流程,旨在确保每个补丁都经过质量审查,并且每个补丁都实现了期望在主线中拥有的变更。这个流程对于小修复可能会很快,但对于大型和有争议的变更,可能会持续数年。许多开发者的挫败感来自对这个流程的理解不足,或者试图规避它。
为了减少这种挫败感,本文档将描述补丁如何进入内核。下面的介绍描述了一个在某种程度上理想化的流程。更详细的处理将在后面的部分中进行。
补丁经历的阶段通常包括:
- 设计。这是补丁的真正需求以及如何满足这些需求的方式的阐述。设计工作通常是在不涉及社区的情况下进行的,但最好在可能的情况下在公开进行这项工作;这可以节省后期重新设计的时间。
- 早期审查。补丁被发布到相关的邮件列表,该列表上的开发者会回复任何可能的评论。如果一切顺利,这个过程应该能发现补丁的任何主要问题。
- 更广泛的审查。当补丁接近准备好纳入主线时,它应该被相关子系统维护者接受——尽管这种接受并不保证补丁会最终进入主线。补丁将出现在维护者的子系统树中,并进入-next树(下面描述)。当这个流程运作时,这一步将导致对补丁的更广泛审查,并发现由于将这个补丁与其他工作集成而导致的任何问题。
- 请注意,大多数维护者也有日常工作,因此合并您的补丁可能不是他们的最高优先级。如果您的补丁收到需要更改的反馈,您应该进行这些更改,或者证明为什么它们不应该被更改。如果您的补丁没有审查意见,但没有被其适当的子系统或驱动程序维护者合并,您应该坚持更新补丁到当前内核,以便它能够干净地应用,并继续发送它进行审查和合并。
- 合并到主线。最终,一个成功的补丁将被合并到由Linus Torvalds管理的主线存储库中。此时可能会出现更多的评论和/或问题;开发者对这些评论做出响应并修复任何问题是很重要的。
- 稳定发布。现在受到补丁影响的用户数量很大,因此可能会出现新问题。
- 长期维护。虽然开发者合并代码后可能会忘记它,但这种行为往往会给开发社区留下不好的印象。合并代码减轻了一些维护负担,因为其他人会修复由API更改引起的问题。但原始开发者如果希望代码在较长时间内保持有用,就应该继续对代码负责。
内核开发者(或其雇主)最大的错误之一是试图将流程简化为一个“合并到主线”的步骤。这种方法通常会导致所有涉及方的挫败感。
2.3. 补丁如何进入内核
只有一个人可以将补丁合并到主线内核存储库中:Linus Torvalds。但是,例如,在2.6.38内核中,超过9500个补丁中只有112个(约1.3%)是由Linus自己直接选择的。内核项目早已发展到一个规模,单个开发者不可能独自检查和选择每个补丁。内核开发者应对这种增长的方式是通过围绕信任链构建的副手系统。
内核代码库在逻辑上被分解为一组子系统:网络、特定架构支持、内存管理、视频设备等。大多数子系统都有一个指定的维护者,负责该子系统内的代码。这些子系统维护者在某种程度上是该子系统代码的守门人;他们通常会接受将补丁纳入主线内核。
子系统维护者各自管理着自己的内核源代码树,通常(但绝对不总是)使用git源代码管理工具。像git(以及类似的工具,如quilt或mercurial)这样的工具允许维护者跟踪补丁列表,包括作者信息和其他元数据。在任何给定时间,维护者可以确定他或她的存储库中的哪些补丁在主线中找不到。
当合并窗口打开时,顶级维护者将要求Linus“拉取”他们选择要从其存储库合并的补丁。如果Linus同意,补丁流将流入他的存储库,成为主线内核的一部分。Linus对特定补丁在拉取操作中的关注程度不同。很明显,有时他会非常仔细地查看。但一般规则是,Linus相信子系统维护者不会向上游发送糟糕的补丁。
子系统维护者反过来可以从其他维护者那里拉取补丁。例如,网络树是由首先在专门用于网络设备驱动程序、无线网络等树中积累的补丁构建的。这种存储库链可以是任意长的,尽管它很少超过两三个链接。由于链中的每个维护者都信任管理较低级别树的人,这个过程被称为“信任链”。
显然,在这样的系统中,将补丁纳入内核取决于找到合适的维护者。直接将补丁发送给Linus通常不是正确的做法。
2.4. 下一个树
子系统树的链路指导着补丁流入内核,但这也引发了一个有趣的问题:如果有人想要查看为下一个合并窗口准备的所有补丁,该怎么办?开发人员会对其他待定的变更感兴趣,以查看是否存在任何需要担心的冲突;例如,改变核心内核函数原型的补丁将与使用该函数旧形式的任何其他补丁发生冲突。审阅者和测试人员希望在所有这些变更进入主线内核之前以集成形式访问这些变更。可以从所有有趣的子系统树中拉取变更,但这将是一项繁重且容易出错的工作。
答案以“-next”树的形式出现,其中子系统树被收集以进行测试和审查。由 Andrew Morton 维护的较旧的这些树称为“-mm”(代表内存管理,这是它开始的方式)。-mm 树集成了来自长列表的子系统树的补丁;它还有一些旨在帮助调试的补丁。
此外,-mm 包含了一系列由 Andrew 直接选择的补丁。这些补丁可能已经发布在邮件列表上,或者它们可能适用于内核的某个部分,而该部分没有指定的子系统树。因此,-mm 充当了一种最后的子系统树;如果没有其他明显的路径将补丁引入主线,它很可能最终会出现在 -mm 中。在典型的开发周期中,大约 5-10% 的补丁通过 -mm 进入主线。
当前的 -mm 补丁可以在“mmotm”(-mm 的当下版本)目录中找到:
https://www.ozlabs.org/~akpm/mmotm/
然而,使用 MMOTM 树可能是一种令人沮丧的体验;它明显有可能甚至无法编译。
下一个周期补丁合并的主要树是由 Stephen Rothwell 维护的 linux-next。Linux-next 树是按设计的,是主线在下一个合并窗口关闭后预期的样子的一个快照。Linux-next 树在组装时会在 linux-kernel 和 linux-next 邮件列表上宣布;它们可以从以下地址下载:
https://www.kernel.org/pub/linux/kernel/next/
Linux-next 已经成为内核开发过程的一个重要部分;在给定的合并窗口期间合并的所有补丁应该在合并窗口打开之前真正地进入 linux-next。
2.5. 分级树
内核源代码树包含 drivers/staging/ 目录,其中许多驱动程序或文件系统的子目录都在等待被添加到内核树中。它们在 drivers/staging 中保留,因为它们仍需要更多的工作;一旦完成,它们就可以移入内核适当的位置。这是一种跟踪不符合 Linux 内核编码或质量标准的驱动程序的方式,但人们可能希望使用它们并跟踪开发。
Greg Kroah-Hartman 目前维护着分级树。仍需要工作的驱动程序被发送到他那里,每个驱动程序在 drivers/staging/ 中都有自己的子目录。除了驱动程序源文件外,该目录中还应该有一个 TODO 文件。TODO 文件列出了驱动程序需要接受到内核适当位置所需的待定工作,以及应该为驱动程序的任何补丁抄送的人员名单。当前的规则要求贡献给分级的驱动程序必须至少能够正确编译。
分级可以是将新驱动程序相对容易地引入主线的一种方式,在那里,有幸的话,它们将引起其他开发人员的注意并迅速改进。然而,进入分级并不是结束故事的地方;在分级中没有看到持续进展的代码最终将被移除。发行版也倾向于相对不愿意启用分级驱动程序。因此,分级最多只是成为成为一个适当主线驱动程序的中间站。
2.6. 工具
从上文可以看出,内核开发过程严重依赖于能够将各种补丁集中引导到不同的方向。如果没有合适强大的工具,整个过程将远不如现在运行得那么顺利。关于如何使用这些工具的教程远远超出了本文档的范围,但可以提供一些指引。
迄今为止,内核社区主要使用的源代码管理系统是 git。Git 是自由软件社区中正在开发的一系列分布式版本控制系统之一。它在处理大型代码库和大量补丁时表现良好,因此非常适合内核开发。尽管随着时间的推移,它的学习和使用难度有所降低,但它仍然以难以学习和使用而闻名。对于内核开发人员来说,对 git 的某种熟悉几乎是必需的;即使他们不使用它进行自己的工作,他们也需要使用 git 跟上其他开发人员(以及主线)的步伐。
现在几乎所有的 Linux 发行版都打包了 git。它有一个主页:
该页面有指向文档和教程的链接。
在不使用 git 的内核开发人员中,最受欢迎的选择几乎肯定是 Mercurial:
https://www.selenic.com/mercurial/
Mercurial 与 git 共享许多功能,但它提供了一个许多人认为更易于使用的界面。
另一个值得了解的工具是 Quilt:
https://savannah.nongnu.org/projects/quilt/
Quilt 是一个补丁管理系统,而不是源代码管理系统。它不跟踪随时间的历史;相反,它的定位是跟踪针对不断发展的代码库的一组特定变更。一些主要的子系统维护者使用 quilt 管理打算上游的补丁。对于某些类型的树(例如 -mm),quilt 是最适合的工具。
2.7. 邮件列表
大量的 Linux 内核开发工作是通过邮件列表进行的。要成为社区的一个完全运作的成员,加入至少一个邮件列表是必要的。但 Linux 邮件列表也代表着对开发人员的潜在危险,他们有可能被埋在一堆电子邮件下,违反 Linux 邮件列表上使用的惯例,或者两者兼而有之。
大多数内核邮件列表都在 vger.kernel.org 上运行;主列表可以在以下地址找到:
http://vger.kernel.org/vger-lists.html
然而,也有一些在其他地方托管的列表;其中一些在 redhat.com/mailman/listinfo 上。
内核开发的核心邮件列表当然是 linux-kernel。这个列表是一个令人畏惧的地方;每天的邮件数量可能达到 500 封,噪音很大,对话可能非常技术性,参与者并不总是关心表现出高度的礼貌。但没有其他地方能像这个列表一样将内核开发社区作为一个整体聚集在一起;避开这个列表的开发人员将会错过重要的信息。
有一些提示可以帮助在 linux-kernel 上生存:
- 将列表传送到一个单独的文件夹,而不是你的主邮箱。一个人必须能够在持续一段时间内忽略这些邮件流。
- 不要试图跟踪每一次对话 - 没有其他人这样做。重要的是要过滤感兴趣的主题(尽管要注意,长时间运行的对话可能会偏离原始主题而不更改电子邮件主题行),以及参与的人员。
- 不要喂食怼人者。如果有人试图激起愤怒的回应,忽略他们。
- 在回复 linux-kernel 邮件(或其他列表上的邮件)时保留所有相关人员的抄送(Cc:)头。在没有强烈理由(比如明确的请求)的情况下,你永远不应该删除收件人。始终确保你回复的人员在抄送列表中。这个惯例也使得不需要明确要求在回复你的帖子时抄送你。
- 在提问之前搜索列表存档(以及整个网络)。一些开发人员可能会对那些明显没有做好功课的人感到不耐烦。
- 使用交错(“inline”)回复,这样你的回复更容易阅读。(即避免顶部回复 -- 将你的答案放在你要回复的引用文本之上的做法。)更多细节,请参阅 Documentation/process/submitting-patches.rst。
- 在正确的邮件列表上提问。Linux-kernel 可能是一个总的会议地点,但它不是找到所有子系统开发人员的最佳地方。
最后一点 - 找到正确的邮件列表 - 是初学者常犯错误的地方。在 linux-kernel 上问一个与网络相关的问题几乎肯定会收到一个礼貌的建议,让他们去 netdev 列表上问,因为那是大多数网络开发人员经常访问的列表。其他列表是为 SCSI、video4linux、IDE、文件系统等子系统存在的。寻找邮件列表的最佳地方是在打包内核源代码的 MAINTAINERS 文件中。
2.8. 开始内核开发
关于如何开始内核开发过程的问题是常见的 - 无论是个人还是公司。同样常见的是一些错误,这些错误使得开始的阶段比必要的更加困难。
公司通常希望雇佣知名的开发人员来启动一个开发团队。这实际上可能是一种有效的技术。但这也往往是昂贵的,并且并不能为有经验的内核开发人员增加太多。通过投入一点时间,可以使公司内部的开发人员了解 Linux 内核开发,这是可能的。花这些时间可以使雇主拥有一群了解内核和公司的开发人员,并且可以帮助培训其他人。从中期来看,这通常是更有利可图的方法。
个人开发人员通常会对从哪里开始感到困惑。从一个大型项目开始可能会令人生畏;人们通常希望先从一些较小的项目开始试水。这是一些开发人员跳入修复拼写错误或次要编码风格问题的补丁的开始之处。不幸的是,这些补丁会产生一定程度的噪音,这对整个开发社区来说是分散注意力的,因此它们越来越不受欢迎。希望向社区介绍自己的新开发人员将不会通过这种方式得到他们所希望的接待。
Andrew Morton 给那些渴望成为内核开发人员的建议是:
对于所有内核初学者来说,第一项目肯定应该是“确保内核在你能接触到的所有机器上始终完美运行”。通常的做法是与其他人一起努力解决问题(这可能需要坚持!),但这没关系 - 这是内核开发的一部分。
(https://lwn.net/Articles/283982/).
在没有明显问题需要解决的情况下,建议开发人员查看当前的回归列表和一般的未解决问题列表。永远不会缺少需要解决的问题;通过解决这些问题,开发人员将在这个过程中获得经验,同时也会在开发社区中建立尊重。