当Linux5.8候选版本最近开放测试时,最大的新闻不是它里面有什么,而是它的大小。正如莱纳斯·托瓦尔兹自己指出的那样,“尽管没有任何一样东西能真正让…脱颖而出。5.8看起来是我们有史以来最大的版本之一。“。
诚然,RC5.8提供了超过14,000个非合并提交,大约800,000行新代码,并添加了大约100个新贡献者。它之所以变得如此庞大,可能仅仅是因为多亏了新冠肺炎,很少有人去旅行,而且我们都能够在发布窗口完成比往常更多的工作。但是,从这位经验丰富的Linux内核贡献者和维护者的角度来看,5.8RC版本特别引人注目的是,它前所未有的大小对于那些维护它的人来说并不是问题。我认为,这是因为Linux拥有世界上所有软件项目中最好的工作流程。
拥有最好的工作流程意味着什么?对我来说,归根结底是Linux内核开发人员随着时间的推移建立的一组基本规则,这些规则允许他们在大规模的过程中取得坚持不懈的稳定和可靠的进展。
值得从Linux的一些历史开始。在该项目的早期(1991-2002年),人们只是简单地将补丁直接发送给Linus。然后,他开始从子维护人员那里拉来补丁,而这些人会从其他人那里拿补丁。情况很快就变得很明显,这是不能扩大规模的。每件事都太难跟踪了,项目经常面临合并不兼容代码的风险。
这导致Linus探索了各种变更管理系统,包括BitKeeper,它采用了一种异常分散的方法。与其他变更管理系统使用签出/修改/签入协议不同,BitKeeper为每个人提供了整个repo的副本,并允许开发人员发送他们的变更以进行合并。Linux在2002年短暂采用了BitKeeper,但事实证明,它作为专有解决方案的地位与社区对开源开发的信念不相容,这种关系于2005年结束。作为回应,Linus消失了一段时间,并带着git回来了,它将分散的更改管理带向了一个强大的新方向,是管理过程的第一个重要的实例化,正是这个管理过程使Linux开发今天工作得如此顺利。
以下是对Linux内核工作流至关重要的七个最佳实践(或基本原则):
Linux的一个中心原则是,所有的更改都必须分成几个小步骤。您提交的每个承诺应该只做一件事。这并不意味着每个提交的大小都必须很小。对一千个文件中使用的函数的API进行简单更改可以使更改变得巨大,但仍然可以接受,因为这都是执行一项任务的一部分。通过始终遵守这一命令,您可以更容易地识别和隔离任何最终出现问题的更改。它还使修补程序审查者更容易只需担心修补程序完成的单个任务。
不仅应该将所有更改分解为尽可能小的增量,而且它们也不能破坏内核。每一步都需要功能齐全,不能造成倒退。这就是为什么对函数原型的更改还必须更新调用它的每个文件,以防止构建中断。因此,每一步都必须作为独立的改变来进行,这就把我们带到了下一个要点:
如果在某个时候发现了错误,您需要知道是哪项更改导致了问题。从本质上讲,平分是一种允许您找到所有错误的确切时间点的操作。
要做到这一点,您可以转到最后一个已知的正在工作的提交所在的位置,以及已知的第一个被破坏的提交的位置,并在该位置测试代码。如果有效,您将前进到下一个中间点。如果没有,则返回到另一个方向的中间点。通过这种方式,您可以在十几次左右的编译/测试中找到将代码从数万个可能的提交中分离出来的提交。Git甚至通过git二等分功能帮助自动执行此过程。
重要的是,这只有在您遵守前面的规则时才能很好地工作:每次提交只做一件事。否则,您将不知道问题提交中的许多更改中的哪一个导致了问题。如果COMMIT中断了构建或未启动,而对等分落在该提交上,则您将不知道二等分的方向。这意味着您永远不应该编写依赖于将来提交的提交,比如调用尚不存在的函数,或者更改全局函数的参数而不更改同一提交中的所有调用方。
Linux工作流进程不允许您重新设置其他人使用的任何公共分支的基础。一旦重新基址,重新基址的提交将不再与基于该树的存储库中的相同提交相匹配。如果公共树不是树层次结构中的叶子,则不得对其进行基址调整。否则,它将破坏层次结构中较低的树。当一个GIT存储库基于另一个树时,它构建在该树中的提交之上。REBASE替换了提交,可能会删除其他树所基于的提交。
正确地进行合并远不是既定的。其他变更管理系统合并来自不同分支的代码是一场噩梦。它通常以难以解决的冲突而告终,并且需要大量的人工工作才能解决。Git被构造为毫不费力地完成这项工作,Linux因此直接受益。这在很大程度上解释了5.8版本的大小并不是什么大事。5.8-rc1版本平均每天提交200次,合并总数从5.7次增加到880次。一些维护人员注意到了更多的工作负荷,但是没有什么会带来太大的压力或会导致精疲力竭。
不幸的是,这可能是许多其他项目跳过的最基本的最佳实践之一。每个提交都需要是独立的,这包括它的提交日志。理解正在进行的更改所需的所有内容都必须在更改的提交日志中进行解释。我发现我的一些最长、最具描述性的更改日志是针对单行提交的。这是因为单行更改可能用于非常微妙的错误修复,且该错误修复应该在ChangeLog中进行详细描述。
在提交更改几年后,任何人都不太可能知道为什么要进行更改。Git指责可以显示哪些提交更改了文件的代码。其中一些承诺可能非常古老。也许您需要删除锁,或者更改某些代码,但您并不确切地知道它存在的原因。针对该代码更改编写良好的ChangeLog有助于确定该代码是否可以删除或如何修改。有几次我很高兴我在代码上写了详细的更改日志,因为我必须删除代码,而更改日志的描述让我知道我的更改是可以进行的。
最后,一个基本的实践是运行持续测试和持续集成。在向上游发送请求之前,我会对每个请求进行测试。我们还有一个名为Linux-Next的复制品,它会收集维护人员在其存储库的特定分支上所做的所有更改,并对它们进行测试,以确保它们正确集成。实际上,Linux-Next运行整个内核的一个可测试分支,该分支将用于下一个版本。这是一个公开的回购,所以任何人都可以测试它,这是经常发生的-人们现在甚至发布关于Linux代码的bug报告-Next。但结果是,已经在Linux-NeXT上运行了几周的代码很可能会很好地进入主线。
所有这些实践都使Linux社区能够在9周的常规时间表上如此大规模地发布令人难以置信的可靠代码(每个版本平均提交10,000次,上一次发布超过14,000次)。
我想指出另一个因素,这是我们成功的关键:文化。内核社区中有一种持续改进的文化,正是这种文化让我们首先采用了这些实践。但我们也有信任的文化。我们有一条明确的途径,人们可以通过它做出贡献,并随着时间的推移证明他们都愿意也有能力推动项目向前发展。这建立了一个值得信赖的关系网络,这些关系一直是该项目长期成功的关键。
在内核层,我们别无选择,只能遵循这些做法。所有其他应用程序都在内核之上运行。内核中的任何性能问题或bug都会成为顶部应用程序的性能问题或bug。所有错误路径必须和平退出;否则,整个系统将受到威胁。我们关心每一个错误,因为风险是如此之高,但这种心态将很好地服务于任何软件项目。
应用程序可能只会因为错误而崩溃。这会惹恼用户,但风险并不高。高质量的软件不应该对错误掉以轻心。这就是为什么Linux开发工作流被认为是要遵循的黄金标准。
关于作者:Steven Rostedt(@srostedt)是Linux内核贡献者和VMware的开源工程师。您可以通过blogs.vmware.com/opensource或Twitter上的@VMWopensource了解有关Steven工作的更多信息