2. 开发流程是如何运作的

在 20 世纪 90 年代初,Linux 内核开发是一件相当自由散漫的事情,参与的用户和开发者数量相对较少。随着用户基数达到数百万,并且在一年内有大约 2,000 名开发者参与,内核此后不得不演化出许多流程,以保持开发顺利进行。为了有效地参与其中,必须深入理解这一流程是如何运作的。

2.1. 宏观概述

Linux 内核采用一种松散的基于时间轴的滚动发布开发模型。一个新的内核大版本发布(例如,我们称之为 9.x)[1] 每两到三个月进行一次,伴随着新功能、内部 API 更改等。一个典型的发布包含大约 13,000 个变更集,涉及数十万行代码的修改。最近的发布及其日期可以在 维基百科 上找到。

在每次发布的补丁合并方面,遵循着一个相对简单的规则。在每个开发周期的开始,据说“合并窗口”是开放的。此时,被认为足够稳定(且被开发社区接受)的代码会被合并到主线内核中。新开发周期的大部分更改(以及所有重大更改)都将在此期间合并,合并速率接近每天 1,000 个更改(“补丁”或“变更集”)。

(顺便一提,值得注意的是,在合并窗口期间集成的更改并非凭空出现;它们是提前收集、测试和暂存的。该过程的具体运作方式将在后面详细描述)。

合并窗口大约持续两周。在此期间结束时,Linus Torvalds 会宣布窗口关闭,并发布第一个“rc”内核。例如,对于注定成为 9.x 的内核,在合并窗口结束时发布的版本将被称为 9.x-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 稳定版发布

开发者如何决定何时关闭开发周期并创建稳定版本?使用的最重要的指标是以前版本的回归列表。任何 bug 都不受欢迎,但那些破坏了过去正常工作的系统的 bug 被认为是特别严重的。因此,导致回归的补丁不受欢迎,并且极有可能在稳定期被撤销。

开发者的目标是在发布稳定版之前修复所有已知的回归。在现实世界中,这种完美很难实现;如此规模的项目中变量实在太多了。总会有一个时候,推迟最终发布只会使问题变得更糟;等待下一个合并窗口的更改堆积得越来越多,从而在下一次造成更多的回归。因此,大多数内核发布时都带有少数已知的回归,不过希望其中没有一个是严重的。

一旦发布了稳定版本,其日常维护就会移交给“稳定团队”,目前该团队由 Greg Kroah-Hartman 和 Sasha Levin 组成。稳定团队将使用 9.x.y 编号方案对稳定版本发布不定期的更新。

要考虑纳入更新版本的补丁必须满足:(1)修复了一个重大 bug,以及(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 内核的 9,500 多个补丁中,只有 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)中找到

不过,使用 MMOTM 树可能会是一次令人沮丧的经历;它甚至根本无法编译的可能性是相当大的。

下一个周期补丁合并的主树是 linux-next,由 Mark Brown 维护。按设计,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 电子邮件(或其他列表上的邮件)时,请保留所有相关人员的 Cc: 头。在没有强有力的理由(例如明确请求)的情况下,您绝不应该删除收件人。始终确保您正在回复的人在 Cc: 列表中。这种惯例也使得没有必要明确要求在对您帖子的回复中抄送您。

  • 在提问之前,请搜索列表存档(以及整个网络)。对于那些明显没有做功课的人,一些开发者可能会失去耐心。

  • 使用交织式(“内联”)回复,这会使你的回复更容易阅读。(即避免顶部发帖——即把你的回答放在你所回复的引文上方的做法。)有关更多详细信息,请参阅 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/).

在没有明显的要修复的问题的情况下,建议开发者查看当前的回归列表和开放的 bug 列表。从不缺乏需要修复的问题;通过解决这些问题,开发者将在获得流程经验的同时,树立在其余开发社区中的威望。