2. 开发过程如何运作¶
20世纪90年代早期的 Linux 内核开发相当松散,参与的用户和开发者数量相对较少。随着用户群达到数百万,并且一年中有大约 2,000 名开发者参与其中,内核必须演变出许多流程来保持开发的顺利进行。要成为其中的有效组成部分,必须对流程的运作方式有扎实的理解。
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 更改等的主要内核版本。一个典型的版本可能包含大约 13,000 个变更集,对数十万行代码进行了更改。5.x 是 Linux 内核开发的领先优势;内核使用滚动开发模型,不断集成重大更改。
在每个版本的补丁合并方面,遵循着相对直接的原则。在每个开发周期的开始,被称为“合并窗口”打开。届时,被认为足够稳定(并且被开发社区接受)的代码会被合并到主线内核中。新开发周期的大部分更改(以及所有重大更改)将在此时合并,速率接近每天 1,000 个更改(“补丁”或“变更集”)。
(顺便提一下,值得注意的是,在合并窗口期间集成的更改不是凭空产生的;它们是提前收集、测试和暂存的。稍后将详细介绍该过程是如何工作的)。
合并窗口持续大约两周。在此时间结束时,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 版本的最终稳定更新。
一些内核被指定为“长期”内核;它们将获得更长时间的支持。请参阅以下链接,查看活动长期内核版本及其维护者的列表
选择一个内核进行长期支持纯粹取决于维护者是否有维护该版本的需要和时间。目前尚无关于任何特定即将发布的版本进行长期支持的已知计划。
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 树¶
子系统树的链引导补丁流入内核,但也提出了一个有趣的问题:如果有人想查看正在为下一个合并窗口准备的所有补丁怎么办?开发人员会对其他待处理的更改感兴趣,以查看是否有任何需要担心的冲突;例如,更改核心内核函数原型的补丁将与任何使用该函数旧形式的其他补丁冲突。审核人员和测试人员希望在所有这些更改进入主线内核之前以集成形式访问这些更改。人们可以从所有感兴趣的子系统树中拉取更改,但这将是一项庞大且容易出错的工作。
答案以 -next 树的形式出现,其中收集子系统树以进行测试和审查。这些树中较旧的,由 Andrew Morton 维护,被称为“-mm”(代表内存管理,这是它的由来)。-mm 树集成了来自长列表的子系统树的补丁;它还有一些旨在帮助调试的补丁。
除此之外,-mm 还包含 Andrew 直接选择的大量补丁。这些补丁可能已发布在邮件列表中,或者可能适用于没有指定子系统树的内核部分。因此,-mm 作为一种最后的手段的子系统树运行;如果没有其他明显的补丁进入主线的路径,它很可能会最终进入 -mm。在 -mm 中累积的杂项补丁最终会被转发到适当的子系统树或直接发送给 Linus。在典型的开发周期中,大约有 5-10% 的进入主线的补丁是通过 -mm 进入的。
当前的 -mm 补丁可在“mmotm”(-mm of the moment)目录中找到,网址为
使用 MMOTM 树可能是一种令人沮丧的体验;它有很大可能甚至无法编译。
下一个周期补丁合并的主要树是 linux-next,由 Stephen Rothwell 维护。根据设计,linux-next 树是主线在下一个合并窗口关闭后预期外观的快照。linux-next 树在组装完成后会在 linux-kernel 和 linux-next 邮件列表上宣布;它们可以从以下网址下载
Linux-next 已成为内核开发过程中不可或缺的一部分;在给定的合并窗口期间合并的所有补丁实际上应该在合并窗口打开之前的一段时间内进入 linux-next。
2.5. Staging 树¶
内核源代码树包含 drivers/staging/ 目录,其中包含许多用于驱动程序或文件系统的子目录,这些驱动程序或文件系统正准备添加到内核树中。它们在 drivers/staging 中保留,因为它们仍需要更多工作;一旦完成,它们就可以移动到内核本身。这是一种跟踪不符合 Linux 内核编码或质量标准的驱动程序的方式,但人们可能希望使用它们并跟踪开发。
Greg Kroah-Hartman 当前维护 staging 树。仍需要工作的驱动程序会被发送给他,每个驱动程序在 drivers/staging/ 中都有自己的子目录。除了驱动程序源文件外,该目录中还应该存在一个 TODO 文件。TODO 文件列出了驱动程序需要接受到内核本身的工作,以及应该抄送任何驱动程序补丁的人员列表。当前的规则要求贡献给 staging 的驱动程序至少必须正确编译。
Staging 可能是将新驱动程序引入主线的一种相对简单的方法,运气好的话,它们会引起其他开发人员的注意并迅速改进。但是,进入 staging 并非故事的结束;在 staging 中没有看到定期进展的代码最终将被删除。分发者也往往相对不愿意启用 staging 驱动程序。因此,staging 充其量只是成为合适的上线驱动程序途中的一个停靠站。
2.6. 工具¶
从上面的文本可以看出,内核开发过程在很大程度上取决于将各种方向的补丁集合进行管理的能力。如果没有足够强大的工具,整个过程就不会像现在这样顺利。有关如何使用这些工具的教程超出了本文档的范围,但可以提供一些提示。
到目前为止,内核社区使用的主要源代码管理系统是 git。Git 是自由软件社区中正在开发的许多分布式版本控制系统之一。它非常适合内核开发,因为它在处理大型存储库和大量补丁时表现良好。它还以难以学习和使用而闻名,尽管随着时间的推移它已经有所改进。熟悉 git 几乎是内核开发人员的必备条件;即使他们不将它用于自己的工作,他们也需要 git 来跟上其他开发人员(和主线)正在做的事情。
Git 现在由几乎所有的 Linux 发行版打包。有一个主页位于
该页面有指向文档和教程的指针。
在不使用 git 的内核开发人员中,最受欢迎的选择几乎肯定是 Mercurial
Mercurial 与 git 共享许多功能,但它提供了许多人认为更容易使用的界面。
另一个值得了解的工具是 Quilt
Quilt 是一个补丁管理系统,而不是源代码管理系统。它不跟踪随时间变化的历史记录;相反,它的目标是跟踪针对不断演变的代码库的特定更改集。一些主要的子系统维护者使用 quilt 来管理旨在上游的补丁。对于管理某些类型的树(例如 -mm),quilt 是完成这项工作的最佳工具。
2.7. 邮件列表¶
大量的 Linux 内核开发工作是通过邮件列表完成的。如果不加入至少一个列表,就很难成为社区中完全合格的成员。但是,Linux 邮件列表也代表着对开发人员的潜在危害,他们可能会被大量电子邮件淹没,违反 Linux 列表上使用的约定,或者两者兼而有之。
大多数内核邮件列表托管在 kernel.org 上;主列表可以在以下网址找到
还有其他地方托管的列表;请查看 MAINTAINERS 文件,了解任何特定子系统的相关列表。
内核开发的核心邮件列表当然是 linux-kernel。这个列表是一个令人望而生畏的地方;每天的邮件量可以达到 500 条,噪音很高,对话可能是非常技术性的,而且参与者并不总是关心表现出高度的礼貌。但是,没有其他地方像这样将内核开发社区作为一个整体聚集在一起;回避此列表的开发人员将会错过重要的信息。
有一些提示可以帮助你在 linux-kernel 中生存
将列表传递到单独的文件夹,而不是你的主邮箱。必须能够忽略长时间的邮件流。
不要尝试跟踪每一次对话 - 没有人会这样做。重要的是根据感兴趣的主题进行筛选(尽管请注意,长时间的对话可能会偏离原始主题,而不会更改电子邮件主题行)以及参与者。
不要喂巨魔。如果有人试图激起愤怒的回应,请忽略他们。
在回复 linux-kernel 电子邮件(或其他列表上的电子邮件)时,请保留所有相关人员的抄送:标题。在没有充分理由的情况下(例如明确的要求),你永远不应删除收件人。始终确保你正在回复的人在抄送:列表中。此约定也使得无需明确要求在回复你的帖子时抄送给你。
在提问之前,请搜索列表档案(以及整个网络)。一些开发人员可能会对那些显然没有做功课的人不耐烦。
使用交错(“内联”)回复,这使得你的回复更容易阅读。(即,避免顶部发布 - 将你的答案放在你正在回复的引用文本之上的做法。)有关更多详细信息,请参阅Documentation/process/submitting-patches.rst。
在正确的邮件列表上提问。Linux-kernel 可能是总体会议点,但它不是查找所有子系统开发人员的最佳地点。
最后一点 - 找到正确的邮件列表 - 是初级开发人员常犯的错误。 如果有人在 linux-kernel 邮件列表中提出与网络相关的问题,他们几乎肯定会收到一个礼貌的建议,让他们改在 netdev 邮件列表中提问,因为大多数网络开发人员都在那里活动。 其他的邮件列表还存在于 SCSI、video4linux、IDE、文件系统等子系统中。 查找邮件列表的最佳位置是内核源代码附带的 MAINTAINERS 文件。
2.8. 开始内核开发¶
关于如何开始内核开发过程的问题很常见 - 无论是来自个人还是公司。 同样常见的是一些失误,这些失误会使得最初的关系比它本应有的样子更加艰难。
公司通常会聘请知名的开发人员来启动开发团队。 事实上,这可能是一种有效的技术。 但它往往也很昂贵,并且对于扩大有经验的内核开发人员的储备并没有太大的作用。 只要投入一些时间,就可以让内部开发人员快速掌握 Linux 内核开发。 花费这些时间可以使雇主拥有一批既了解内核又了解公司的开发人员,并且他们还可以帮助培训其他人。 从中期来看,这通常是更有效益的方法。
个人开发者通常会不知所措,不知道从哪里开始。 从一个大型项目入手可能会让人感到畏惧;人们通常会希望先从一些较小的项目入手来试探一下。 在这一点上,一些开发人员会开始创建修复拼写错误或次要代码样式问题的补丁。 不幸的是,这样的补丁会产生一定程度的噪音,对整个开发社区造成干扰,因此,它们越来越不被认可。 希望通过这些方式向社区介绍自己的新开发人员不会得到他们所希望的那种接待。
Andrew Morton 为有抱负的内核开发人员提供了以下建议
The #1 project for all kernel beginners should surely be "make sure
that the kernel runs perfectly at all times on all machines which
you can lay your hands on". Usually the way to do this is to work
with others on getting things fixed up (this can require
persistence!) but that's fine - it's a part of kernel development.
(https://lwn.net/Articles/283982/)。
在没有明显的错误需要修复的情况下,建议开发人员查看当前回归列表和一般开放的错误。 永远不乏需要修复的问题;通过解决这些问题,开发人员将在获得过程经验的同时,建立起其他开发社区的尊重。