2. 开发流程如何运作

1990年代早期,Linux 内核的开发过程相当松散,涉及的用户和开发者相对较少。如今,内核的用户基数已达数百万,一年内约有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 变更等。一个典型的版本可能包含大约 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 稳定版发布

开发者如何决定何时关闭开发周期并创建稳定版本?最主要的衡量标准是之前版本的回归列表。虽然不欢迎任何 bug,但那些破坏过去正常运行系统的 bug 被认为特别严重。因此,导致回归的补丁不受欢迎,并且在稳定期很有可能被撤销。

开发者的目标是在稳定版发布之前修复所有已知的回归。但在现实世界中,这种完美很难实现;对于如此规模的项目,变量实在太多了。总有一个时候,延迟最终发布只会让问题变得更糟;等待下一个合并窗口的变更堆积会越来越大,下次会带来更多的回归。因此,大多数 5.x 内核都会带有一些已知回归发布,尽管希望它们都不是严重的。

一旦发布了稳定版,其持续维护将移交给“稳定团队”,目前由 Greg Kroah-Hartman 负责。稳定团队将使用 5.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 内核的 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 邮件(或其他列表上的邮件)时,请保留所有相关人员的 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/)。

在没有明显问题需要修复的情况下,建议开发者查看当前的回归列表和一般的开放错误。需要修复的问题从不缺乏;通过解决这些问题,开发者将在过程中获得经验,同时,在开发社区中赢得尊重。