如何参与到 Linux 社区中来

开源之道一再强调社区才是开源的真正奥秘所在,赢得社区就赢得开源,但想要赢得社区,需参与到社区中来,2018年我们花了很大的篇幅介绍了广义的参与社区篇,2019年则就具体的开源项目社区来做文章,当仁不让,Linux 作为迄今为止全球最大的开源项目,是应该被第一个介绍和学习的。

Sat Mar 9, 2019 | 33800 Words | 大约需要阅读 68 分钟 | |

一、内核开发流程指南

本文档旨在帮助开发者(以及他们的经理们)如何以最小的代价参与到内核的开发社区中来,试图解释Linux社区是如何运转的,进而告诉那些对Linux内核开发(或者,更深入的说,普遍意义的自由软件开发)不熟悉的人们如何去做,虽然这里有一些技术资料,但这是一个针对流程方面的讨论,不需要深入了解内核编程就可以理解,这点请毋须担心。

内容摘要

本节剩余部分覆盖的内容包括:内核开发流程、开发人员及其雇主可能遇到的各种坑。解释内核代码为什么应该合并到官方(“主线”)中的各种原因: 自动可用到用户、多种形式的社区支持、以及有能力影响社区开发的方向等。为Linux内核做代码贡献还必须承认 GPL兼容的许可协议。

第二节介绍开发流程、内核发布周期、以及合并窗口的机制。涵盖了补丁开发,审查和合并周期中的各个阶段。有一些关于工具和邮件列表的讨论。我们鼓励希望开始使用内核开发的开发人员将bug作为初步练习进行跟踪和修复。

第三节则为大家介绍项目早期的计划,重点在于尽快的让开发者参与到开发社区中来。

第四节是关于编码流程的,讨论人们经常遇到的陷阱。介绍了补丁的一些要求,以及一些有助于确保正确生成内核补丁的工具。

第五节讨论发布补丁以供审核的过程。要得到开发社区的认真对待,必须对补丁进行适当的格式化和描述,并且必须将它们发送到正确的地方,遵循本节中的建议,将有助于确保开发者的补丁能够被最快的接收。

第六节覆盖了发布了补丁之后发生的事情,因为工作并不是说发完补丁就万事大吉了。和审核代码的开发者一起是开发流程中相当重要的一部分,本节提供了许多关于如何在这个重要阶段避免问题的技巧。千万要谨记,代码即使是合并到主干,也并不表示所有的工作已经完成。

第七节则介绍了一些“高级”的主题,使用git管理补丁并查看其他人发布的补丁。

第八届介绍了内核开发的更多关于指向源的文档信息。

本文档是关于什么的

Linux 内核,一款代码超过6百万行、拥有1000多活跃贡献者、世上现有的最大、最活跃的自由软件项目。Linux 自从1991年诞生以来,该内核已发展成为同类中最佳的操作系统核心,它不仅运行在袖珍数字音乐播放器、台式机、现有最大的超级计算机乃至介于两者之间的所有类型的系统上。它是一种强大,高效,可扩展的解决方案,几乎能够适用于所有计算机体系架构。

伴随着 Linux 内核项目的快速增长,相应的希望参与到其中来的开发人员(和公司)的数量也在增长。硬件供应商希望确保 Linux 能够得到很好地支持他们的产品,从而让这些产品对Linux用户更具吸引力。而嵌入式供应商,则将Linux作为组件整合到他们的产品中,希望Linux尽可能满足系统当前的任务。而Linux发行版厂商以及其它的软件供应商则更加希望Linux在可靠性、高性能、灵活性方面有突出的能力。最后是最终用户,也同样希望对Linux进行适当的调整,进而满足自己的独特需求。

Linux最引人注目的特性之一就是让所有的开发人员都可以访问它;任何具备相应技能的人都可以去改进Linux,并尝试影响其未来的发展方向。那些专有的产品是无法提供这样的开放性的,这是自由软件的一个独特特性。其实,话说回来,Linux 内核相比于其它绝大多数的自由软件,更具开放性。最好的证据莫过于:每三个月的内核开发周期,要牵涉到超过1000多名开发人员,他们分别来自100多家不同的公司(当然也有的根本就没有公司)。

在内核开发社区中工作其实并不特别的困难,但是,话虽然这么说,仍然有很多的潜在的贡献者认为在创始参与的时候觉得困难。这么多年,内核社区已经相对成熟了,并发展出了自己独特的操作方式,从而能够在每天变更数千行代码的环境中顺利运行(并生成高质量的产品)。因此,Linux内核开发过程与专有开发方法有很大不同,这并不奇怪。

内核的开发过程可能对新人来说有些陌生,有时还会带着一丝的恐慌,这都算正常现象,尽管如此,内核已经用事实证明,这些都不是事。一个不了解内核社区的方式的开发人员(或者更糟糕的是,试图蔑视或逃避)将会在整个过程中,产生令人沮丧的体验。但是一定要明白一个道理:开发社区虽然对于愿意学习的人有莫大的帮助,但是也就到此为止了,对于那些不愿意花时间认真倾听或不关心开发过程的人来说,内核社区只能表示惋惜。

希望阅读本文档的人能够避免那些让人痛苦不堪的经历。本文包含很多的处理细节,如果可能的话,还是要及时的去亲子体验一把。内核开发社区一直缺人,尤其是那些能够帮助内核变得更好的开发者;以下内容可以帮助到你(又或者是为你工作的开发者),加入到内核社区中来。

感谢

本文档由Jonathan Corbet 所撰写,其邮箱地址:corbet@lwn.net。以及James Berry、Alex Chiang、Roland Dreier、Randy Dunlap、Jake Edge、Jiri Kosina、Matt Mackall、Amanda McPherson、Andrew Morton、以及 Jochen Voß 等的改进。

该文档的撰写得到了Linux基金会的大力支持,尤其要感谢 Amanda McPherson,是他看到了这项工作的价值,并让这一切变为现实。

将代码并入主干的重要性

总有一些公司和开发人员偶尔会问起:为什么他们应该学习如何使用内核社区并将他们的代码放入主干(“主干”即是有Linus Torvalds所维护,也是很多linux分发版的基础)。从短期来看,为内核上游社区贡献代码看起来有点得不偿失,似乎是保持现有的分支就可以了,且可以直接支持用户。而事实上,时间稍长即可证明,基于现有的分支(“脱离主干”)会付出很大的代价。

那么我们就来说说脱离主干(out-of-tree)的代价是什么,以下列表从内核开发流程上的一小部分,其中大部分内容将在本文档后面详细讨论。考虑如下情形:

  • 合并到内核主干的代码是供所有 Linux 用户使用。它将自动出现在使用它的所有发行版中。不需要驱动程序磁盘,下载或支持多个版本的多个发行版的麻烦;这一切都适用于开发人员和用户。换句话说,并入主干解决了大部分的分发、支持问题。
  • 虽然内核开发人员在努力的维护和用户空间接口,希望保持稳定,但是事实上,内核内部的 API 仍在不断的变化。缺乏稳定的内部界面是一个深思熟虑的设计决策;它允许随时进行基本改进,并产生更高质量的代码。基于该策略的一个结果就是,那些脱离主干的分支如果要使用新的内核,就要不断的从主干中获取这些代码,即需要不断的进行维护,而这需要大量的工作才能保持代码正常的工作。

相反,在主干中的代码就没有这些额外的工作,如果有 API 的变更,开发者们就会立即在主干中进行修复和处理。所以,合并到主干的代码在维护成本上具有显著的优势。

  • 除此之外,内核中的代码通常会被其他开发人员改进。有的时候,用户社区和客户所改进的产品,往往惊喜不断。
    • 内核的代码在合并到主干之前和之后均是审核的,无论这些原创开发人员的技能有多强,这个审核过程总会能找到可以改进代码的地方。通常情况下,这样的审核都能够发现Bug以及安全方面的问题。对于在封闭环境中开发的代码尤其如此,某种程度上讲,linux内核非常受益于这些外部的开发人员的审核。脱离主干(Out-of-tree)的代码将意味着质量难以保证。
  • 开发者只要参与到内核的开发中来,就可能会影响到项目的发展方向。如果说用户只是在旁边抱怨的话,真的没有啥意思,不如让事情按照自己的意愿去发生。那些活跃的开发者在社区拥有极强的声音 —— 以及改进的能力,这可以让内核满足他们自身的需求来进行发展和进化。
  • 当分支代码被单独维护时,始终存在由第三方提供类似功能的不同实现的可能性。如果发生这种情况的话,合并代码将变得更加困难 —— 甚至到了几乎不可能的地步。真到了那个时候,就面临着令人非常不爽的选择:要么选择无限期地保持脱离主干的非标准特征,要么是放弃这些脱离主干的代码,然后回迁到主干版本。
  • 代码的贡献是整个流程的最为基本的行为,这也是之所以现有的流程能够良好运作的前提。通过贡献代码,开发者可以向内核添加自己想要的功能,也是对于其他内核开发人员同样有实际用处。如果你已经为Linux写过代码(或者正在考虑这样做),那么您显然对该平台的持续成功感兴趣;贡献代码是帮助确保成功的最佳方式之一。

以上所有的可能结果,都是脱离主干(out-of-tree)所导致,也包括那些以专有的形式、仅发行二进制的方式。但是,在考虑任何类型的仅二进制内核代码分发之前,还应考虑其他因素。这些包括:

  • 发行封闭的内核模块会让法律问题的阴影所笼罩。相当多的内核版权持有者认为,大多数仅二进制模块都是内核的派生产品,而这么做的结果就是,他们的再发行违反了 GNU 通用公共许可证(下文会做详尽的解释)。当然本文的作者并不是一名律师,所以请谨慎对待,本文档中的任何内容都不能被视为是法律建议。关于那些闭源的二进制内核模块的法律问题只能由法院去做决定。但是事情毫无疑问,明白在这里的是这些模块的不确定性是存在的。
  • 二进制模块大大增加了调试内核问题的难度,大多数内核开发人员都不会做这样徒劳无功的尝试。因此,仅发布二进制模块将会使得贵司的用户几乎无法获得社区的支持。
  • 对于仅二进制模块的分发者来说,支持也更加困难,他们必须为每个分发版本以及他们希望支持的每个内核版本都提供相应的模块。这导致的结果就是,这样一个模块想要全面覆盖相应的正在支持中的内核版本的话,就需要分别做十几个版本,而每次升级内核时,都要单独这么做。
  • 上面这几条内容,可以覆盖到所有的闭源的代码中。由于代码是无法获得的,所以社区根本就无法对他们进行审核,这毫无疑问是有可能带来严重问题的。

特别是嵌入式系统的制造商,可能会试图忽视本节所说的大部分内容,因为他们认为他们正在使用一种使用 frozen 的内核版本,且认为在发布后不需要做更多开发的自包含产品。这一做法忽略了广泛的代码审查的价值以及允许用户为其产品添加功能的价值。但这些产品的商业寿命有限,之后必须发布新版本。直到这个时候,恐怕这些供应商才能够明白,如果他们所维护的代码是主干是多么的好!重要的是新产品可以快速的上市。

开源之道注: 开源有今天的成功,其中尊重知识产权等法律在其中起着最为关键的作用!首先我们要谈的就是许可证的事情。

许可证

Linux内核允许开发者以各式各样的许可证方式下来贡献代码,但是所有这些代码必须与GNU通用公共许可证(GPLv2)的版本2兼容,后者是覆盖内核发布的许可协议。在实践中,这意味着所有代码贡献都由GPLv2(可选地,允许在更高版本的GPL下分发的语言)或三款BSD许可证涵盖。任何不属于兼容许可证的贡献都不会被内核接受。

对于内核的代码,不需要(或请求)版权分配。合并到主线内核的所有代码保留其原始所有权;也就是说,现在的内核拥有数千名所有者。

这种所有权结构的一个含义是,任何改变内核许可的尝试都几乎确定注定要失败。很少有实际情况可以获得所有版权所有者的同意(或者从内核中删除他们的代码)。因此,特别是,在可预见的未来,没有可能迁移到GPL版本3。

所有贡献给内核的代码都必须是合法的自由软件。因此,匿名(或假名)贡献者的代码将不被接受。所有贡献者都需要“签署”他们的代码,声明代码可以在GPL下与内核一起分发。未由其所有者许可为自由软件的代码,或者为内核创建与版权相关的问题(例如源自缺乏适当保护措施的逆向工程的代码)的代码无法获得提交的。

有关版权相关问题的问题在Linux开发邮件列表中是蛮常见的事情。这些问题通常不会得到答案,这里特别提醒的是,回答这些问题的人不是律师,也不能提供法律建议。如果看官有与Linux源代码相关的法律问题,那么就应该去和那些理解该领域的律师去沟通、请教。依靠在技术邮件列表上获得的答案是一件讨人嫌弃的事情。

二、开发流程是如何工作的

Linux 内核在早期的阶段(1990年代)开发相当的松散,当然用户和开发人员数量也是相对少的多。后来,凭借数百万用户群和一年内约2,000名开发人员的参与,内核不断发展壮大,必须修改流程以保持平稳发展。为了更为有效的成为内核开发的一份子,有必要对开发过程的工作原理有充分的了解。

宏观视野

内核开发人员使用松散的基于时间的发布过程,每两到三个月发布一次新的主要内核版本。最近的发布版本如下所示:

版本号日期
2.6.26July 13, 2008
2.6.25April 16, 2008
2.6.24January 24, 2008
2.6.23October 9, 2007
2.6.22July 8, 2007
2.6.21April 25, 2007
2.6.20February 7, 2007

每个2.6.x版本都是一个主要的内核版本,包含新功能,内部 API 变更等。典型的2.6版本包含了超过10,000个变更集,其中包含数十万行代码的更改。2.6因此是Linux内核开发的前沿;内核使用滚动开发模型,该模型不断整合主要变更。

关于每个版本的补丁的合并,遵循相对简单的规则。在每个开发周期的开始,“合并窗口”被认为是开放的。那时,被认为足够稳定(并且被开发社区接受)的代码被合并到内核主干代码中。在此期间,新开发周期(以及所有主要更改)的大部分更改将以每天接近1,000个更改(“修补程序”或“变更集”)的速率合并。

另外,值得注意的是,在合并窗口期间集成的更改不是凭空而来的;它们已经被提前收集、测试并演练。这个过程如何工作将在后面详细描述)。

一般合并窗口会持续两周的时间,在这段时间结束时,Linus Torvalds 将声明该窗口已关闭并释放第一个 rc 版内核。例如,对于已经商量好的 2.6.26 的内核,在合并窗口结束时产生的版本将被称为 2.6.26-rc1-rc 的出现意味着合并新功能的时间已经过去,下一个版本的内核稳定期也应该开始。

在接下来的六到十周内,只有修复问题的补丁才能提交给主干。有时会允许更重大的变化,但这种情况很少见;尝试在合并窗口之外合并新功能的开发人员往往会不受待见。这里还有一条一般性的约定俗成的规则:如果开发人员实现了某些功能但却恰好错过了合并窗口,那么只能等到下一个周期再进行合并。(偶尔例外的情况是添加原来不支持的硬件驱动程序,如果他们没有涉及到in-tree的代码,那么也就不会导致回归的问题,另外就是随时要保证安全第一。)

随着修复的补丁不断的进入主干,修补率也将随着时间的推移而减慢。这个时候,Linus就会每周发布一次新的内核;在内核被认为足够稳定并且最终的2.6.x版本发布之前,正常的系列将在-rc6和-rc9之间的某个位置上升。这个时候也是下一轮的重新开始。

我们来举一个现成的实例,以下表格是 2.6.25 的开发周期( 均在2008年年内发生):

日期版本号
1月24号2.6.24 稳定版发布
2月10日2.6.25-rc1,合并窗口关闭
2月15日2.6.25-rc2
2月24日2.6.25-rc3
3月4日2.6.25-rc4
3月9日2.6.25-rc5
3月16日2.6.25-rc6
3月25日2.6.25-rc7
4月1日2.6.25-rc8
4月11日2.6.25-rc9
4月16日2.6.25 稳定版发布

那么问题来了,开发人员如何决定何时关闭开发周期并创建稳定版本?使用的最重要指标是以前版本的回归列表。没有错误是受欢迎的,但那些打破过去工作的系统被认为是特别严重的。出于这个原因,导致回归的补丁被认为是不利的,并且很可能在稳定期间被恢复。

开发人员的目标是在稳定版本发布之前修复所有已知的回归。而事实上,要完美的实现这个是不现实的,因为如此之大规模的项目有太多的不确定因素存在了。延迟最终版本只会让问题变得更糟;因为这样,等待下一个合并窗口的大量更改将变得更大,下一个版本会创建更多的回归。所以大多数2.6.x内核都会出现一些已知的回归,但希望它们都不是严重的。

一旦稳定的版本已经完成,那么它的维护工作将会被传递给另外一个专门的团队—— “维稳团队”,目前由Greg Kroah-Hartman和Chris Wright两位来维护,稳定的团队将使用 2.6.x.y 编号方案来发布稳定版本的更新。要考虑更新版本的话,补丁必须(1)修复重大错误,并且(2)已经合并到下一个开发内核的主干中。以下继续我们的 2.6.25 版本的示例,历史(撰写本文时)是:

日期版本号
5月1日2.6.25.1
5月6日2.6.25.2
5月9日2.6.25.3
5月15日2.6.25.4
6月7日2.6.25.5
6月9日2.6.25.6
6月16日2.6.25.7
6月21日2.6.25.8
6月24日2.6.25.9

稳定版本的内核维护大约是在半年左右的时间,在那之后,内核的维护工作则由发行商自行进行维护。

补丁的生命周期

补丁是不会直接从开发者本地的开发环境中就跑到内核的主干中的,相反,一些非正式的流程的设计为了确保每个补丁都是被审核过的、质量经过严格把关的、以及确保每个补丁是对主干所进行的更改,如果是较次要的修复的话,这个过程会非常的快,而如果是那些较大的、又是有争议的变更,则要慢的多,有的甚至会持续很多年。有很多的开发者对于补丁的流程是缺乏了解的,有的甚至心存侥幸,试图绕过这个过程。

为了帮助开发者,本章节将讲述补丁进入内核的详细流程,以下内容是从实践中抽取出来步骤,颇为的理想化,但有助于理解。其中的可能遇到的一些问题的应对办法后面还会针对性的讲述。

一个补丁通常会经历如下一些阶段:

  • 设计: 这是补丁实际需求的来源,以及满足这些需求的实现,均是有相应的套路的,设计的工作通常社区是不参与的,但是如果这些工作是公开的话最好了,因为这样的话可以节省大量的时间来进行重新设计。
  • 早期审核:最初的补丁发到相应的邮件列表,在该列表中的开发人员会查看补丁,并对补丁进行评论,回复给作者,如果是正常情况的话,补丁有什么重大缺陷会被找出来。
  • 更大范围的审核:当补丁接近主干所覆盖的内容时,它就会被相关的子系统维护者接受,当然这个接受仅是在子系统范围内有效,并不一定能够合并到主干。该补丁将会合并到子系统维护者的分支以及staging分支(下面有讲到),当流程走到这里的时候,补丁会被接受更为广泛的审核,因为要和其它的系统进行集成,以尽早的发现问题。
  • 合并到主干: 最终,一个有效的补丁会被合并到由 Linus Torvalds 所维护的主干当中,此时可能会出现更多评论和/或问题;重要的是,开发人员应对这些问题做出响应并解决出现的任何问题。
  • 稳定版发布: 被接受的补丁将会被更多的用户所使用,因此还可能会产生更多的新问题。
  • 长期维护版本:开发人员往往认为合并到主干并发布就完事大吉,事实上不应该是这样,负责到底才是正确的做法。合并代码会引入新的维护任务,因为由API 更改引起的问题,需要其他人来进行修复。但是,如果要在长期内保持有效,初始开发人员应继续对代码负责。

内核开发人员(或他们的雇主)犯下的最大错误之一就是尝试将流程缩减为“合并到主干”单一的这样一个步骤。这里给出的忠告是:内核开发没有捷径可走,如果非得这么做,撞到南墙不要怪我们没有提醒你。


2019.4.10 发布公众号(上)

补丁是如何被接受的

这个世界上目前只有一个人可以将补丁合并到内核的主干,他就是:Linus Torvalds,但是话虽然怎么说,但是实际的情形是这样的,拿2.6.25来举例,这个版本有超过12000个补丁,其中只有250个(整体的2%)是由Linus自己亲自选取的。内核项目发展到今天这样的规模,已经不可能有那个人(类)可以做到查看全部的补丁,而且也没有任何意义。内核的开发人员们是基于信任链的中心化的组织。

内核代码从逻辑上分为若干个子系统:网络、特定的架构支持、内存管理、视频设备等等,大多数的子系统都有特定的维护者,该维护者会对所在子系统的代码负责。这些子系统的维护者可以说是看护内核的守门人(没有任何的特定组织和制度),他们(通常情况下)会将补丁核准进入内核的主干中。

子系统的维护者均拥有自身版本的内核源码,多数情况使用的是 git 源代码管理工具,诸如 git(以及quiltmercurial等相关工具)这样的工具允许维护者跟踪补丁列表,包括作者信息和其他元数据。无论何时,维护者都能够识别到,主干中的某个补丁是否是出自自己的仓库。

每当合并窗口处于开放状态时,顶级的维护者会请求 Linus 去”pull”那些他们已经经过挑选的可以合并的补丁,如果 Linus 同意了,这些补丁就会进入到 Linus 的分支仓库,即成为主干的一部分,Linus 本人对于不同的子系统所分配的时间或注意力也是不一样的,要根据实际情形而定。不过可以确定的一定是,他很可能会关注到你。当然,作为一般性的规则,Linux 是对子系统的维护者完全信任的,相信他们可以把好这个关,而不是将糟糕的代码放在主干。

那么,子系统的维护者也可以从其它的维护者哪里获得补丁。举例来说,网络的分支的补丁最初可能是来自诸如网络设备驱动、无线网络等,这个仓库链可以拉的很长,尽管绝大多数时候,这个不会超过三个链。在这个链中的每个维护者都信任来自较低层的代码分支,因此这个过程也被称之为“信任链”。

显而易见的是,在这样一个系统当中,一个补丁进入内核的决定者在于有合适的维护者,将补丁直接发送给 Linus 并不是一个好主意,很少有人这么去做。

Staging 分支

在上一节当中,我们介绍了补丁进入内核的整个流程,尤其是“信任链”是非常的出彩,但是聪明的读者一定发现了一个问题:如果有人想查看为下一次合并窗口所准备的补丁该怎么办?因为我们知道开发人员会对其他待更改的内容感兴趣,大家都会担心自己写的代码是否会遭遇冲突。例如,更改核心内核函数原型的补丁将与使用该函数的旧形式的任何其他补丁冲突。审核人员和测试人员希望在所有这些更改落入主干之前,能够被提前访问。开发者可以从所有自己感兴趣的子系统分支去获取更改,但是这将是一个巨大的工作量,而且还特别容易出错。

问题的答案就是:去 Staging 分支查看。该分支就是专门收集子系统中要合并的补丁来进行测试和审核的。该分支是内核中历史最长的分支之一,由 Andrew Morton 所维护,称之为“-mm”(因为最初是专门针对内存管理来用的),-mm 分支会整个来自所有子系统中的补丁,以及一些旨在有助于调试的补丁。

除此之外,-mm 分支还包含了由 Andrew 亲自挑选的补丁集合,这些补丁可能已发布在邮件列表中,或者它们可能适用于没有指定子系统树的内核部分。因此,-mm 是子系统分支的最后集结点,如果一个补丁没有其它明确的路径进入主干,那么它很可能就止步于这里。在-mm中累积的杂项补丁最终将被转发到适当的子系统树,或者是直接发送给 Linus。在一个典型的开发周期里,进入主干的补丁大约有10%是通过 -mm 分支的。

当前的 -mm 补丁是可以在内核网站的首页找到的:linux-next,那些想要看到-mm当前状态的人可以得到“当下的-mm”树,可以访问:http://userweb.kernel.org/~akpm/mmotm/(开源之道注:已经不可访问。)但是,使用 MMOTM 树可能是令人沮丧的经历;无法编译通过的几率非常之高。

最近开始的另一个 Staging 分支是 linux-next,由Stephen Rothwell维护。根据设计,linux-next树是下一个合并窗口关闭后主干预期的样子的快照。Linux-next 分支会在组装时在linux-kernel和linux-next邮件列表中公布;它们可以从这里下载:linux-next,关于 linux-next 的信息,还可以到https://www.kernel.org/doc/man-pages/linux-next.html

即使是 linux-next 分支本身,也在不断的发生着变化,因为它要不断的适应开发过程,在撰写本文时,涉及linux-next(2.6.26)的第一个完整开发周期即将结束;到目前为止,它已被证明是在合并窗口开始之前查找和修复集成问题的宝贵资源。有关linux-next如何设置2.6.27合并窗口的更多信息,请参见http://lwn.net/Articles/287155/

一些开发人员已经开始建议将 linux-next 作为未来开发的主要分支,linux-next 分支确实远远超出主干,并且更能代表新的改进将合并到主干。这个想法的缺点是linux-next的波动性往往使它成为一个艰难的发展目标。有关此主题的更多信息,请参见http://lwn.net/Articles/289013/,请密切关注该话题,因为还有很多因素涉及到 linux-next。

工具篇

从前面谈到的内核开发中,我们可以得知整个内核的开发过程是取决于将来自四面八方的补丁有效整合的能力。如果没有合适的强大的工具支撑,这整个项目的协作恐怕就是空谈。关于如何使用这些工具的教程远远超出了本文档的范围,这里仅仅提供一些指导性的内容。

到目前为止,内核社区使用的主要源代码管理系统是 gitGit 是自由软件社区中开发的众多分布式版本控制系统之一。它非常适用于内核的开发,因为它在处理大型存储库和大量补丁时表现卓越。它也因难以学习和使用而闻名,尽管随着时间的推移它已经变得非常不错了。对于内核开发来说,掌握git的使用是必备技能,因为即使开发者不使用它来进行日常的内核开发,最后也要面临和其他开发人员的同步问题以及合并到主干等。

Git 现在已经整合到所有的Linux发行版当中了,其官方站点:http://git-scm.com/ 。该网站有详尽的文档和教程,另外,关于针对Linux内核特定的Git知识可以参考:http://linux.yyz.us/git-howto.html。在不使用git的内核开发人员中,最受欢迎的选择几乎可以肯定是Mercurial:http://www.selenic.com/mercurial/,Mercurial 和 Git 有很多相似的特性,但是它在界面上使用更加的方便。

另外一个值得推荐的工具是Quilt:http://savannah.nongnu.org/projects/quilt/。Quilt 是一款补丁管理系统,而并非源代码管理系统,它并不会追踪所有的历史内容。相反,它是针对不断发展的代码库跟踪一组特定的更改。一些主要的子系统的维护者使用 Quilt 来管理提交到上游的补丁的,对于特定的tree来说,(如 -mm)Quilt可以高效的完成任务。

邮件列表

大量的Linux内核开发工作都是通过邮件列表来完成的。如果说哪位开发者没有加入任何的邮件列表,那么ta一定对于社区成员的理解还差了些。当然,邮件列表也不是十全十美的工具,至少它有这样两个弊端:大量的邮件信息把开发者吞噬掉;或者是违反了某些约定,有时候甚至二者都会有。

大多数的邮件列表都运行在 vger.kernel.org之上,主要的邮件列表:http://vger.kernel.org/vger-lists.html,也有一些其它的列表,如lists.redhat.com。

内核开发的核心邮件列表当然是linux-kernel。没见过世面的看到该列表,一定会被惊到,它每天的可以达到500封以上的邮件,大多数是技术的细节,正因为如此,所以讨论的参与者们并不介意诸如礼貌之类的事情。但是没有其他地方可以将内核开发社区整体结合在一起;不订阅此列表的开发人员将错过很多重要的信息。

这里为大家提供一些参与Linux内核邮件列表的技巧:

  • 将订阅列表单独放到某个目录下,不要显示在邮箱主界面。以在特定的时间段内毋须关注。
  • 不要试图去关注所有的话题 ———— 没有人能做到。重要的是过滤所关注的主题(尽管请注意长时间运行的对话可能会偏离原始主题而不更改电子邮件主题行)和参与的人。
  • 保持理智,如果有人在愤怒的情绪下争论,最好是忽略他们。
  • 当响应linux-kernel电子邮件(或其他列表上的电子邮件)时,保留所有涉及的Cc:所有参与者的。在没有强烈理由(例如显式请求)的情况下,永远不要去删除任何的收件人。始终确保回复的人员在抄送列表中。此约定还使得无需明确要求复制对帖子的回复。
  • 在提问之前,请先搜索邮件列表归档(以及Google网络)。一些开发人员可能会对那些显然没有完成作业的人感到不耐烦。
  • 要避免置顶情形(将自己的回答置于正在回复的引用文本之上的做法)。这么做会让人难以阅读,也会给人留下非常不好的印象。
  • 在正确的邮件列表中询问。尽管 Linux-kernel 这个列表是一个主要的汇合点,但是它不是从所有的子系统当中找到开发人员的最好的地方。

最后一点,即找到合适的邮件列表 —— 这常常是开发人员会出错的地方,在 linux-kernel 中询问与网络相关的问题的人几乎肯定会收到礼貌的建议,而不是在netdev列表上询问,因为这是大多数网络开发人员经常光顾的列表。 其它诸如 SCSI、Viedeo4Linux、IDE、filesystem等子系统均有属于自己的邮件列表,其实寻找邮件列表最佳的方式就是到内核文件 MAINTAINERS 中去寻找。

内核开发入门

关于如何开始内核开发过程的问题是很常见的,发起问题的有个人也有公司。但是人们往往也并没有耐心等待他人的回答,就开始干了起来,这就导致更为严重的后果,还不如刚开始什么都不知道的好。

企业常见的做法是找一些知名的开发人员来启动开发团队。而事实上,这也是非常有效的一种方式。但是,这往往也是最为昂贵的一种做法,而且对于增加内核开发人员没有直接的效应。企业的另外一种考虑是,增加一些时间的投入,让内部现有的开发人员参与到Linux内核的开发当中来,从长远来看,这份投入是值得的,不仅可以培养出一些深入了解内核的开发人员,要知道这些人还会进而帮助和影响乃至培训更多的人。从中长期来说,这样的做法是最为划算的。

而对于个人开发者而言,往往在从何处入手而踌躇不前,这是完全可以理解的。从一个巨大规模的项目开始,是令人心生畏惧的,不过我们可以从一些小处着手来试着搞定。比如一个蛮不错的起点就是去修复他人的拼写错误或者是不怎么重要的代码风格问题,不过需要提前向你声明的是:这样的补丁会产生一定程度的噪音,这对整个开发社区来说会分散注意力,因此,它们越来越被人们所瞧不起。所以,以这样的方式来开启自己的内核开发之旅,并不能获得他人足够的认可。

Andrew Morton 为那些有志于成为Linux开发者的同学提供了如下一条建议:

对于所有内核初学者来说,首要完成的工作时:“确保内核在所有可以完成任务的机器上始终完美运行”。通常这样做的方法是与其他人一起解决问题(这可能需要持久性!)但这很好 - 它是内核开发的一部分。

http://lwn.net/Articles/283982/

在没有明显问题要解决的情况下,建议开发人员查看当前的回归列表和优先级一般的漏洞。要知道从来就没有什么短期而紧急的问题;通过解决这些问题,开发人员将获得该过程的经验,同时与开发社区的其他人建立尊重。

三、早期规划

在想象 Linux 内核开发项目的时候,人们可能会以为是直接就开始编码的。事实上不是这样的,Linux 内核作为软件项目和其它的项目没有什么差别,即在开始编写第一行代码之前要进行充足的准备工作,而这是所有软件项目成功的基础条件。在早期规划和沟通上花一些时间,会让以后的路更容易走,可以节省更多的时间。

明确问题

和任何工程项目一样,Kernel 的成功,是因为其对于要解决的问题描述非常的清晰。在某种情况下,这点非常容易做到:例如,当需要特定硬件的驱动程序时;但是更多的时候,实际的问题和众人所描述的相差十万八千里,而这往往会导致更多的困难。

举一个多年以前的例子:Linux 音频的开发人员寻求一种运行应用程序的方法,而不会因系统中的过度延迟而导致丢失或其他内容。他们最初采取的解决方案是将音频的内核模块和Linux安全模块(LSM)之间做一个hook,并将此模块配置为授予特定应用程序访问实时调度程序的权限。当完成了该模块的编写之后,发送到了 linux-kernel 邮件列表,没过多久就遇到了问题。

对于音频开发者而言,目前的安全模块足以解决他们的问题,但是,对于更广泛的内核社区来说,它被视为滥用LSM框架(它不是为了将特权授予他们原本不会拥有的进程)以及系统稳定性的风险。内核社区认为的首选解决方案应该是短期内通过限制机制进行的实时调度访问,以及长期持续的延迟减少工作。

然而,音频的开发者并没有去检查他们已经实现的内容,他们也不愿意接受替代的方案;由此产生的分歧使得开发人员对整个内核开发过程感到失望;其中一个在音频的邮件列表中发布了如下内容:

有许多非常优秀的Linux内核开发人员,但他们往往会被一大群傲慢的傻瓜吓跑。向这些人传达用户的需求简直是在浪费时间。他们过于自命不凡,而根本什么都听不进去。

http://lwn.net/Articles/131776/

而实际上完全不是这么回事,内核开发人员更关注系统稳定性,长期维护以及找到问题的正确解决方案,而不是针对特定模块的特定解决办法。这个故事的寓意在于要去关注问题本身—— 而不是特定的解决办法—— 而且要在实际投入代码工作之前,尽可能多的去和开发社区进行充分的讨论。

在此,我们针对打算为内核做出贡献的人给出如下一些忠告:

  • 究竟什么是需要解决的问题?
  • 该问题会波及到那些用户?解决问题应针对那些用例?
  • 当下的内核是如何解决该问题的?

只有回答了上述这些问题之后,然后才去考虑可能的解决方案。

尽早讨论

在规划内核开发项目时,在启动实现之前与社区进行讨论是非常有意义的。早期沟通可以通过多种方式节省时间和麻烦:

  • 很有可能是目前内核的解决方式是开发者还没有理解,Linux 内核是一个非常大的项目,拥有非常多的功能和特性,而且它们往往并不是那么的明显,另外,并不是所有的功能都能文档化,而且还特别容易错过一些具体的功能,比如一位开发者看到一个发布的完整的设备驱动程序,该程序和现有的驱动程序重复了,而新的作者完全没有意识到。重新发明现有车轮的代码不仅浪费;它也不会被主干所接受。
  • 可能存在所提出的解决方案对于主干合是不可接受的因素。在编写代码之前最好找出这样的潜在问题。
  • 其他开发人员完全有可能考虑过这个问题;他们可能有更好的解决方案的想法,并可能愿意帮助创建该方案。

内核发展多年以来,总结出一个非常有意义的教训:内核的代码是在封闭的情况下设计和实现的话,总是存在各种各样的问题,而且这些问题总是在发布到社区之后才能被发现。 而且有的时候更改为严重,这些实现需要花费数月乃至数年的时间才能达到社区的标准。这里列举几个例子:

  • Devicescape网络堆栈是为单处理器系统设计和实现的。在它适用于多处理器系统之前,它无法合并到内核的主干当中,将锁定等改造成代码是一项艰巨的任务;因此,此代码(现在称为mac80211)的合并延迟了一年多。
  • Reiser4 文件系统,自身包含了许多功能,这些功能在核心内核开发人员看来,很多已经在虚拟文件系统层中实现了,它还包括在不将系统暴露给用户导致的死锁的情况下无法轻松实现的功能。这些问题本身已经说明,更不用说开发者拒绝解决其中的一些问题,这明显导致 Reiser4 远离了主干。
  • AppArmor 安全模块,被认为采用了不安全和不可靠的方式使用内部虚拟文件系统的数据结构。尽管后来代码进行了大幅的修改,但是离主干依旧很远。

以上所有这些案例,都以惨痛的代价告诉我们,要尽早的去和其他的内核开发人员进行充分的沟通和讨论,可以节省大量的时间,可以避免那些让人抓狂的痛苦经历。

你都和谁聊过

当开发者决定将其所规划的内容公布时,接下来要做的事情就是:从哪里开始?答案其实蛮简单:找到对的邮件列表,以及对的维护者。对于邮件列表来说,就是我们在前面也提及的:最好的方法到 MAINTAINERS 文件中查找。如果在该文件中有相应的子系统列表的话,就直接到该地址去发邮件即可,这要胜过跑到 linux-kernel 主列表发送;因为在相应的子系统中有更大机会接触到相关的开发人员。

找到维护者确实是有点困难,重要的事情说三遍,MAINTAINERS 文件是个不错的开始。尽管该文件有的时候,并不能做到及时的更新,甚至有时候还可能发生有的子系统根本就没有在这里被提及。更有甚者,MAINTAINERS 所列出的人,可能早已不在当前担任任何的角色了。因此,当对于联系谁有疑问时,一个有用的技巧就是使用 git(特别是git log) 来查看当前的活跃情况,该命令可以看到是谁撰写的补丁,以及谁(如果有的话)将签名附加在补丁上。这些人才是最适合帮助开发新的项目的开发者。

如果你试过了所有的方法,还是无法找到对应的维护者,那么你就去找一下 Andrew Morton,和他聊聊,这也是追踪特定代码片段维护者的有效方法之一。

什么时候提交?

要尽可能的在项目的早期发布规划,这对于内核开发者来说,百利而无一害。在规划中描述正在解决的问题以及打算如何实现等等,换句话说,开发者提供的任何信息都可以帮助到开发社区。

其实,事情可能出乎大家的意料,在这个阶段也就是在开发者公开了自己的规划之时,令人沮丧的可能不是有人反对,而是很少甚至根本就没有人理会。这样惨烈的局面的造成可能的原因有:

  1. 内核开发者真的都太忙了。
  2. 规划写的过于”宏伟”,代码实现很少,这样的情况,多数开发者是不会搭理你的。
  3. 没有人有义务或责任去审核其他开发者的提案的。

如果请求评论发布几乎没有产生评论,请不要认为这就意味着人们对项目没有兴趣。当然,你也不能想当然的以为,你的规划是没有问题的。如果发生这样的情况,最好的办法是继续追踪,保持和社区的紧密联系。

获得正式接纳

如果开发者的工作是在自己公司里的环境中完成的,(当然,大多数Linux内核的工作都是这样),显而易见,有一项重要的工作需要去做,那就是将公司的代码发布到公开邮件列表之前,要获得所在公司的许可。在GPL兼容许可下发布尚未清除的代码可能会造成问题;公司的管理层和法律人员越早就内核开发项目的发布达成一致意见,那么对于每个人都是有益处的。

一些读者可能正在考虑将他们在内核的工作去支持尚未得到官方认可的产品中。在公共邮件列表上公布其雇主的计划可能不是一个好的办法。当发生这样的情况时,笔者的建议是去为公司保密。这不是那种我们通常意义上说的闭门造车,要注意它们之间的本质区别。

当然,也有一些公司在开发早期就透漏了自己的计划,没法做到封闭,那么拥有经验丰富的内核开发人员的公司就会选择以开放的方式来发展,因为他们预见到了集成的问题会更加的严重。对于没有这种内部专业知识的公司,最好的选择是聘请外部开发人员,进而根据保密协议去审查计划。Linux 基金会有一个 NDA 计划,旨在专门帮助公司解决这种情况;更多信息可在以下网址找到:

http://www.linuxfoundation.org/en/NDA_program

该审查通常情况下足以避免以后出现严重问题,而且还不用公开披露公司的规划。

四、获得代码相关的权利

虽然对于一个可靠的、面向社区的设计过程有很多要说的,但是最终要证明事实的仍然是代码本身,这里所说的代码是指那些要由其他开发人员检查并合并(或者不合并)到主干的代码,因此,这些代码至关重要,它直接决定着项目的成功与否。

本节将为大家讲解关于编码的流程。首先,我们来看看内核开发人员经常出错的环节,然后将聚焦于 转向正确的事情以及可以帮助完成任务的工具。

陷阱

  • 代码风格

Linux 内核一直以来都有可以说是相当标准的代码风格,具体描述在内核文档 Documentation/CodingStyle。但是,在绝大多数时候,这份文档更像是一份指南,而不是必须执行。那现实的情况就是,内核中存在大量不符合编码风格准则的代码。该代码的存在会给内核开发人员带来两种不祥的兆头。

首先就是那些认为内核的编码标准不再重要的人们,他们也不认为没有必要强制执行。然而事实上,如果这些人的代码没有按照标准去编码,那么在合并到主干的过程中就会遭遇重重困难,根据过去的观察,那些开始的时候不注意标准的人都经历了此类重新调整格式的过程。像Linux内核这样巨大无比的项目,代码是需要一些一致性要求的,这样能够使得开发者快速的理解任何部分成为可能,而不是把精力耗费在理解各种稀奇古怪的“混乱风格”。

当然,有的时候会发生内核的编码风格和开发人员所在公司要求的编码习惯发生冲突,这个时候,想要合并到主干仓库的话,公司就需要作出妥协。将代码贡献到上游内核,其实就意味着公司就必须放弃一些诸如控制权之类的 ———— 当然其中会包括代码采用何种的风格。

另外一个常见的陷阱是,会主观的以为当下的内核中已经存在的代码存在风格问题,需要进行修复,开发人员可能会开始生成重新格式化补丁,以此来熟悉流程,或者将其名称添加到内核更改日志中,或者两者兼而有之。但纯粹的编码风格修复会被开发社区视为噪音;这样的做法往往会得到一个冰冷的漠视。在处理功能性代码时顺带修复一下代码样式是很自然的过程,但编码样式的更改不应该提到和功能性一样的等级。

编码风格的文档也不应被视为永远不能违反的绝对法律。如果有充分的理由反对默认风格(例如,如果分割成适合80列限制,那么这条线的可读性会大大降低),就大胆的去做吧。

  • 抽象层

计算机科学的教授们,在为学生们讲课的时候,告诉大家要尽可能的去使用抽象,好处是灵活性更强,且信息还能隐藏,当然,Linux 内核也在大量的使用抽象,如此量级的代码,不使用抽象是不敢想象的,但经验表明,过度或过早的抽象可能与过早优化一样有害。抽象应该用于所需的水平,过犹不及。

在一个简单的层面上,考虑一个函数,该函数的参数总是被所有调用者传递为零。人们可以保留这个观点,以防有人最终需要使用它提供的额外灵活性。但是到那个时候,实现这个额外参数的代码已经被一些从未被注意到的微妙方式打破了 - 因为它从未被使用过。退一步讲,当需要额外的灵活性时,它不会以符合程序员早期期望的方式那样做的。内核开发人员会定期提交补丁以删除未使用的参数;一般来说,它们本来就不应该被预设式的添加。

隐藏访问硬件的抽象层 ———经常允许大量的驱动程序与多个操作一起使用——— 这是经常让人头痛的事情。这些层会使代码模糊不清,并可能造成性能损失;它们不应该属于Linux内核。

另一方面,如果您发现自己从另一个内核子系统复制了大量代码,现在是时候问一下,将一些代码提取到一个单独的库中或者在更高级别实现该功能,是否更有意义?在整个内核中复制相同的代码没有任何价值。

  • ifdef和预处理

C 语言的预处理器强大的功能,让一些C程序员欲罢不能,他们认为它是一种有效地将大量灵活性编码到源文件中的方法。但是预处理器毕竟不是C 语言本身,大量使用预处理器会导致代码难以阅读,也不容易让编译器检查出问题所在。过多的使用预处理器的代码是要进行重构的。

使用#ifdef进行条件编译确实是一个强大的功能,内核也经常会使用到它,但是,几乎很少有人愿意看到代码是被ifdef所覆盖的。所以,内核团队一般的规则是,尽量将使用ifdef的范围局限在头文件中,有条件地编译的代码可以被限制在函数中,如果代码不存在,则这些函数为空。然后,编译器将静默地优化对空函数的调用。这样的话,就会拥有更加清晰的代码,也更容易去遵循。

C预处理器宏存在许多危险,包括可能对具有副作用且无类型安全性的表达式进行多重评估。如果你想要定义宏,还是先考虑一下改为创建内联函数。效果是一样的,但是内联函数更加容易阅读,也不会出现多次评估参数的情况,而且还允许编译器对参数和返回值执行类型检查。

  • 内联函数

内联函数的使用也要提高谨慎,开发者可能会为了一时的方便了放弃了使用函数调用,而是使用内联函数来填充源文件,目的是为了更快的完成工作。但是,这么做会影响到性能。由于这些代码会在每次函数调用都会被复制一次,到最后会让编译后的内核变得非常大。反过来,这会对处理器的内存缓存造成压力,这会大大降低执行速度。基于此,内联函数应该非常的小,而且不鼓励经常使用,毕竟,函数调用的成本并不高;大量内联函数的创建是过早优化的典型例子。

通常情况下,内核开发者会忽略缓存效应的危险。过去在数据结构课程当中讲授的经典时间/空间权衡,在当代硬件的设施中已经不再适用。空间即时间,怎么说一个较大的程序都要慢于紧凑的程序。

在2006年5月,”Devicescape” 网络栈非常高调的准备好进入主干,基于GPL协议,这次捐赠对于社区是非常受欢迎的,因为Linux向来对于无线网络的支持不够尽善尽美,而且 Devicescape 完全可以弥补这些,然而,直到2007年6月,这段代码才真正进入主干(2.6.22)。这中间到底发生了什么?

Devicescape 的代码有着浓重的基于封闭式的开发方式的痕迹,其中一个尤其重大的问题是它并没有支持多处理系统,在此网络栈(现在称为 mac80211)可以合并到主干之前,需要将锁定方案修改以支持多处理器。

曾几何时,Linux 内核代码的开发是不需要考虑多处理器所要求的并发问题的,然而,现在,这份文件是在双核笔记本电脑上编写的。即使在单处理器系统上,提高响应能力的工作也会提高内核中的并发性。所以,那些毋需考虑锁的编码日子一去不复返了。

任何可由多个线程同时访问的资源(数据结构,硬件寄存器等)必须由锁保护。现在编写代码考虑锁是默认的做法,尽管改变是一件蛮困难的事情。内核开发人员应该花时间了解可用的锁定原语,以便为工作选择合适的工具。目前而言,缺乏对并发性的关注的代码将难以进入主干。

  • 回归

值得一提的最后一个危险是:它可能很容易做出改变(这可能会带来很大的改进),这会导致现有用户无法使用。这种变化被称为“回归”,回归在主干内核中变得最不受欢迎。除少数例外情况外,如果无法及时修复回归,则会退回导致回归的更改。最好要避免回归的问题。

人们经常认为,如果一个回归可以让事情对更多的人有用,而只带来少数的问题,那么回归就是合理的。为什么在能为十个系统带来新的功能而只有一个系统崩溃做出改进了呢?Linus在2007年7月表达了对这个问题的最佳答案:

我们不会通过引入新问题来修复错误。这样的方式是疯子行为,因为没有人知道你是否真的取得了任何真正的进行。它是两步前进,后退一步,还是前进一步又后退两步? 摘自(http://lwn.net/Articles/243460/

一种特别不受欢迎的回归类型是用户空间ABI的任何类型的变化。将接口导出到用户空间后,必须无限期地支持它。这一事实使得用户空间接口的创建特别具有挑战性:因为它们不能以不相容的方式改变,所以必须在第一时间完成。因此,始终需要大量的深思熟虑、清晰的说明以及对用户空间接口的大量审核。


2019.4.17 发布公众号(中)

代码检查工具

就目前而言,编写没有任何差错的代码仍然是我们很少有人能够达到的理想境界。但是,我们仍然希望能够做到在代码进入主干之前能够捕获并修复多的错误。基于这样一个理念,内核开发人员经年累月,已经开发了一些非常不错的工具,用来自动化的去捕获各式各样的可能的问题。由计算机所捕获的任何问题都会是在未来不会影响到用户的问题,因此有理由认为应尽可能的使用自动化工具。

首先要注意的是编译器所发出的警告,当下版本的gcc可以检测(并发出警告)大量潜在的错误。在大多数的情况下,这些检测还是准确的,能够指出真正的问题。那些提交给让他人审核的代码理论上是不应该有任何的编译器警告的。当将警告置为无视状态的时候,要特别小心,要尝试去了解这些警告的真实原因,并尽可能的不要干仅仅只是不显示警告的“鸵鸟精神”。

请注意,默认情况下并非所有编译器警告都已启用。使用 make EXTRA_CFLAGS = -W 编译内核以获取完整的集合。

内核本身也提供了一些配置选项,用以打开调试功能,其中大部分的选项都在子菜单kernel hacking下找到,对于用于开发或测试目的来说,应该打开其中几个选项。尤其是下面几个选项:

  • ENABLE_WARN_DEPRECATED, ENABLE_MUST_CHECK,FRAME_WARN,为诸如使用已弃用的接口或忽略函数的重要返回值等问题获取额外的警告,这些警告生成的输出可能很冗长,但不必担心来自内核其他部分的警告。
  • DEBUG_OBJECTS ,将添加代码来跟踪内核创建的各种对象的生命周期,并在事情无序完成时发出警告。如果要添加创建(并导出)其自身的复杂对象的子系统,请考虑添加对对象调试基础结构的支持。
  • DEBUG_SLAB,该选项可以使得内核对于各种内存分配和使用错误的信息,在绝大多数的内核开发上都非常有用。
  • DEBUG_SPINLOCK, DEBUG_SPINLOCK_SLEEP, DEBUG_MUTEXES,会报告大量的关于所的错误信息。

还有很多其他调试选项,其中一些将在下面讨论。其中一些会有着显著的性能影响,不建议一直使用。但是如果是学习的话,还是建议要多多尝试。

其中一个较重要的调试工具就是锁定检查器或叫做“lockdep”。该工具将跟踪系统中每个锁(自旋锁或互斥锁)的获取和释放,相对于彼此获取锁的顺序、当前中断环境等等。然后,它可以确保始终以相同的顺序获取锁,相同的中断假设适用于所有情况,依此类推。换句话说,lockdep可以找到系统在极少数情况下可能出现死锁的特殊情况。对于死锁的情形,若是在生产环境中,无论是对于开发者还是用户都是痛苦不堪的,lockdep可以说是在事前就能够及时的发现潜在问题。开发者应该在提交代码之前(如果有用到锁的话),使用lockdep来运行一下。

作为一名严谨的内核开发者而言,毫无疑问,都会检查可能失败的任何操作(例如内存分配)的返回状态。但事实是,由此产生的故障恢复路径可能完全未经测试。而未经测试的代码往往会出现各式各样的问题,那么,如果说这些错误的处理路径已经被执行了很多次了,那么开发者对于自己的代码就会更有自信。

内核还提供了一个故障诸如的框架,而且是真实有效的,尤其是涉及内存分配的情况。启用故障注入后,将使可配置百分比的内存分配失败;这些故障可以限制在特定范围的代码中。在启用故障注入的情况下运行允许程序员在事情变得糟糕时查看代码如何响应。更多关于该功能的使用请查阅文档:Documentation/fault-injection/fault-injection.txt

使用“sparse”静态分析工具可以找到其他类型的错误。启用sparse,开发者会在如下一些情形时获得警告:

  • 用户空间和内核空间地址的混淆
  • 大小端的差异
  • 整数值传递到指定的位标记
  • …..

工具sparse 必须被单独安装,如果你所使用的发行版里没有带的话,可以到http://www.kernel.org/pub/software/devel/sparse/ 下载,使用make编译即可,当然添加诸如c=1之类的参数,看开发者自己。

通过编译其他体系结构的代码可以最好地找到其他类型的可移植性错误。如果您没有配备S / 390系统或Blackfin开发板,仍然是可以执行编译步骤。可以在以下位置找到大量用于x86系统的交叉编译器:

http://www.kernel.org/pub/tools/crosstool/

尽管安装和使用这些编译器会花费一些时间,但是有助于避免以后出现尴尬的情形。

文档

文档的撰写通常情况下,相比于内核本身的开发规则,就显得有点随意。即便如此,充足的文档将有助于简化新代码与内核的合并,使其他开发人员的工作更轻松,并对您的用户有所帮助。在许多情况下,添加文档基本上是强制性的。

任何补丁的第一个文档都是与其相关的更改日志。日志的条目应当描述正在解决的问题、解决问题的方式、参与补丁的都有谁、对性能的相关影响、以及对于任何有助于理解补丁的描述信息。

任何添加新用户空间接口的代码————包括sysfs、或 /proc 文件———— 都应该撰写相对应的文档,这样可以使得用户空间开发人员能够知道其中的变化。参考 Documentation/ABI/README 文档,获得关于该文档的格式以及需要提供哪些信息。

文件 Documentation/kernel-parameters.txt 介绍了内核启动时的所有参数,任何添加参数的补丁都应在这个文档里描述相应的细节。

任何新配置选项都必须附有帮助文本,该文本清楚地说明了选项以及用户何时可能要选择它们。

对于一些子系统的内部 API 信息,大多是用特殊的注释来进行文档记录的,这些注释可以通过 kernel-doc 脚本的方式提取和格式化。如果作为开发者的你,恰好在使用 kernel-doc 注释的子系统工作,则应该按照它的方式来认真对待,添加详细的内容。即使在没有如此记录的地方,添加kernel-doc评论对将来也是百利而无一害;实际上,这对于初学内核开发人员来说还是一项不错的习惯养成。这些注释的格式以及有关如何创建kernel-doc模板的一些信息可以在Documentation/kernel-doc-nano-HOWTO.txt文件中找到。

阅读大量现有内核代码的任何人都会注意到,注释通常会没有而让人头痛不已。对新代码的期望再次高于过去;合并未注释的代码会更难。也就是说,对于冗长评论的代码几乎是索然寡味。代码本身应该是可读的,其中的注释解释了更加微妙的方面。

理应做到对所有事情都去注释。内存越界的使用应该附加说明,解释为什么那么做是必要的。数据结构的锁定规则也应该在使用的地方添加说明,主数据结构通常需要全面详尽的文档、应指出单独的代码位之间的非显而易见的依赖关系。

任何可能让代码管理员做出不正确的“清理”的事情都需要一个注释,说明为什么就应该这么,如此等等。

内部API 变更

除非在最严峻的情况下,否则内核向用户空间提供的二进制接口不能被突破。相反,内核的内部编程接口非常平滑,可以在需要时进行更改。如果您发现自己不得不解决内核API,或者根本不使用特定功能,因为它无法满足您的需求,这极有可能是 API 需要改变的信号。作为一名内核开发人员,是有权进行此类更改的。

当然,在某些情况下,API 的更改是可以做到的,但是一定要有充足的理由方可。因此,任何进行内部API更改的补丁都应该附有对更改的说明以及必要的原因。而且这种变化也应该分解成一个单独的补丁,而不是隐藏在一个更大的补丁当中。

另一个问题是,更改内部API的开发人员通常负责修复内核树中由更改引起的任何代码的破坏。对于大范围使用的功能,这种任务可能会导致数百或上千个变化 ———— 其中许多可能还与其他开发人员正在进行的工作发生冲突。毋庸置疑,这是一项波及范围很广的工作,因此,最好是确保理由足够的充分。

在进行不兼容的API更改时,应尽可能确保编译器捕获尚未更新的代码。这样可以帮助开发者找到所有使用该接口的代码。而且它还会提醒 out-of-tree 上的开发人员,告诉他们应该做出相应的更改,支持 out-of-tree 代码不是内核开发人员需要担心的,但是也没有必要故意给 out-of-tree 制造麻烦。

五、提交补丁

“丑媳妇,迟早是要见婆娘的”,当开发者的代码经过了审核之后,最终是要合并到主干中的。果不其然,内核开发社区经过这么多年,已经发展出了一套用于发布补丁的约定和流程;遵循这些流程可以让开发者的工作事半功倍。本节内容就是为大家提供补丁的这些内容的,更多信息也可以在内核文档目录中的SubmittingPatchesSubmittingDriversSubmitChecklist 文件中找到。

什么时候提交?

人们总是试图避免在补丁完全“准备好”之前发布补丁。对于简单的补丁来说,这么做是完全可以的。但是,如果说开发工作非常的复杂,那么尽早从社区获得反馈对于完成工作来说是好处多多的。因此,开发者应该尽早考虑将自己正在进行的工作公布出来,乃至让 git 分支可访问,从而让感兴趣的开发人员可以随时进行了解。

在发布尚未考虑包含的代码时,最好在发布本身中这样说。还要提到任何仍有待完成的主要工作和任何已知问题。只要少数的人会去看哪些正在进行中的补丁,但是正是这些人可能会提出建议,进而帮助补丁的发布者在正确的道路上。

在制作补丁之前

在将补丁发送到开发者社区之前要考虑以下几件事情:

  • 尽最大可能的去测试代码,要充分的利用内核所提供的调试工具,这样可以确保内核是合理的通过各种配置选项的组合的,以及是使用交叉编译器构建不同的体系结构的。
  • 确保代码的风格是符合内核编码指导的。
  • 更改是否影响到性能?这样的情形下,就应该跑一跑基准测试,从而以实际数据来查看更改所带来的影响(或改进);另外,补丁中要将测试摘要记录下来。
  • 在发布代码之前,先掂量一下自己在公司的位置,如果不是拍板的人,就不要做,要记得上面特别强调过的,贡献的内核的代码是以GPL发布的。

作为一个非强制的规则,在发布代码之前进行一些深思熟虑的检查,无论是对己还是对社区,都是百利而无一害的,大多数时候还会带来效率的提升。

准备补丁

补丁的准备工作,说实话,确实是相对来说比较大的,但是经过实践证明,这是值得的,所谓的“磨刀不误砍柴工”,想省去这个步骤,即使是短期的工作也是不可取的,只会给工程带来更多的负面。

补丁的准备必须是针对某个具体的内核版本来得,通常是应基于 Linus 所维护的主干版本,但是,其它较大的分支如 --mmlinux-next 亦或是某个子系统等也是可以作为基础版本的,这样可以适应更为广泛的测试和审查。具体则取决于补丁实际的模块类型而定,如果不是基于这些大的分支,那么开发者可能就需要做大量的解决冲突、处理 API更改等无意义的工作。

补丁应该遵循,一系列的变更是有实际的逻辑说法的,哪怕即使最为微小的更改都应去单独的形成一个补丁。这就对于拆分补丁提出了较高的要求,有很多开发者在这方面花费了大量的时间,方能被社区接纳。为了帮助开发者,实现所提交的补丁尽快的被接受,这里为大家提供一些小小的建议:

  • 开发者所发布的系列补丁应该和自己本地版本控制系统中的补丁有着较大的区分,开发者应以主干的方式来完成自己的补丁格式,并按照各自的意义来将补丁拆分。因为内核的开发者们能够看懂的是离散的、自包含的补丁,而不是某个开发者自己个性化环境中的一坨花里胡哨的内容。
  • 每个逻辑上独立的更改都应格式化为单独的补丁,这些更改可能很小(如:”向某结构添加某字段”),也可能非常的大(如:”添加一个新的硬件驱动程序”),但是它们是独立成形的,而且是可用一行语句能够进行描述的,每个补丁都应该进行特定的更改,可以自行查看并验证其执行的操作。
  • 对上述的话进行重新的表述:请不要在同一个补丁中混合使用不同类型的更改。 举例来讲,如果一个补丁修复了一个关键的安全漏洞,但是它却更改了某些数据结构、代码还重新进行了格式化,那么它就又可能被过滤掉,那么这个重要的修复也就同时被pass掉了,得不偿失。
  • 每个补丁都应该做到,有了该补丁之后,内核仍然是可以被正确的构建且无误的运行的。如果开发者的系列补丁在中途被中断,内核应依旧是可以工作的。当使用 git bisect 工具查找回归时,部分应用补丁系列是一种常见的情况; 假如糟糕的事情还是发生了,那么这样的后果是为那些从事追踪问题的开发者和用户来说带来负面的工作量。
  • 但是,也不要过分的细化。最近就发生了这么一件事,一位开发者将一个文件分为500个补丁进行了提交,当然这样的行为会给ta在邮件列表的声誉带来很负面的影响。凡是有个量就好,过犹不及。
  • 使用一系列补丁添加一个全新的基础架构可能很诱人,但要让该基础架构保持未使用状态,直到该系列中的最终补丁启用整个补丁。如果可能的话,还是尽量不要这么去做,如果那个系列增加了回归,那么二分法会将最后一个补丁指向导致问题的补丁,即使真正的错误在其他地方也是如此。只要有可能,添加新代码的补丁应该是立即生效的。

创建一系列完美的补丁是需要付出努力的,并且要花费一定的时间,尤其是在“真正的工作”之后进行的,但是这些都是值得的,因为内核是一个庞大的协作工程。

补丁格式

经过了上面的学习,我们只需要再做一点工作就可以将补丁提交给邮件列表了,那就是补丁的格式,需要将每个补丁格式化的添加一条消息,该消息可以快速、清晰地将其目的传达给内核的其他开发人员。因为,内核的每个补丁均类似于如下的格式:

  • 可选的“From” 行,以补丁的作者命名,只有当你通过电子邮件传递其他人的补丁时才需要这行内容,但是有总比没有好。
  • 描述该补丁是干什么的一行内容。对于没有其他上下文的读者而言,此消息应该足以找出补丁的范围;它是将在“简短形式”更改日志中显示的行。消息首先是以子系统名称来打头,然后是补丁的实际用途。举例如下:
gpio: fix build on CONFIG_GPIO_SYSFS=n
  • 接下来是一个空行,然后紧跟着描述补丁的详细内容。这些描述可以很长,以描述清楚问题为最终原则,它应该说明补丁的作用,以及它为什么应该被内核采纳。
  • 另外要添加一个或多个标记行,但至少应有一个作者签名的行:即补丁的作者是谁。标签我们将会在后面继续详聊。

通常情况下,以上的格式准则应以文本的形式提交到版本控制系统,接下来要做的:

  • 补丁本身采用统一(“-u”)补丁格式。使用diff“-p”选项会将函数名称与更改相关联,从而使得生成的补丁更容易被其他人阅读。

应极力避免在补丁中包含无关文件(例如,构建过程生成的文件或编辑器备份文件)的更改,文档目录中的文件"dontdiff”在这方面可以提供帮助;使用“-X”选项将其传递给diff

标签是用来描述该补丁开发有关的内容,供其他开发人员参考使用。具体的标签在内核文档SubmittingPatches中有详尽的描述,以下是一些简要的总结。标签行应该有如下内容:

tag: Full Name <email address> optional-other-stuff

常用的标签有:

  • Signed-off-by:这是开发人员的通行证,他或她有权提交补丁以包含在内核中。意味着作者是是签署过 内核开发者原创认证 协议的,该协议可以在内核文档SubmittingPatches中找到,没有签署该协议的补丁是不会被主干接受的。
  • Acked-by: 表示另一个开发人员(通常是相关代码的维护者)的协议,该补丁适合包含在内核中。
  • Tested-by:声明该补丁是经过某工程师测试过的,且没有发现任何问题,可以正常工作。
  • Reviewed-by:指定的开发人员已经检查了补丁的正确性;有关更多详细信息,请参阅文档 SubmittingPatches中的审阅者声明。
  • Reported-by:说明该补丁修复的问题的提出者,此标签用于表示对测试我们的代码的人(通常未被充分认可的人)给予信任,并在事情无法正常工作时通知我们。
  • Cc:指定人员收到了补丁的副本,并有机会对其发表评论。

为补丁打标签,请谨慎小心,只有 Cc 是没有被明确规定的,可以随意指定。

发送补丁

当一切都准备好的时候,接下来就是将补丁通过邮件发送出去,有这么几件事情需要适当注意一下:

  • 在发送之前,要确保补丁是完好无损的。邮件客户端有可能会将补丁文件中的空白符、空行进行篡改,这会导致补丁不会被接受,所以在补丁发出之前,要多确认,一定要确保正确无误。

文件Documentation/email-clients.txt 对于邮件客户端的设置是非常有帮助的。

  • 补丁本身也一定要确认不要犯什么低级错误。要确保补丁一直都可以通过scripts/checkpatch.pl的检查,但是也要记得,checkpatch.pl虽然久经考验,也熟悉内核补丁应该的样子,但是它本身毕竟只是一个脚本。如果发生了为了纠正checkpatch.pl所建议的错误,而让事情变得更糟,那么就按照自己的意愿来。

补丁应始终以纯文本的形式发送。请不要将它们作为附件发送;这使得审阅者更难以在回复中引用补丁的各个部分。相反,只需要将补丁放在邮件的正文就好。

邮寄补丁时,将副本发送给可能对其感兴趣的任何人也很重要。与其他一些项目不同,内核鼓励人们在发送多些副本给人们;不要认为相关人员会在邮件列表上看到你的邮件。尤其是如下一类人,应该抄送的:

  • 补丁所涉及到子系统的维护者,如前所述,MAINTAINERS文件是找到这些维护的不二之选。
  • 在同一个代码区域内的其他开发者,尤其是现在仍然在贡献的开发者,利用git来查看都有谁更改过该文件。
  • 如果要对bug的提出者或需求提出者有所响应,把他们也抄送上。
  • 将副本发送到相关的邮件列表,或者,如果没有其他适用,则发送到Linux内核列表。
  • 如果您正在修复错误,请考虑修复是否应该进入下一个稳定更新。如果是这样,stable@kernel.org应该获得补丁的副本。还要在补丁本身的标签中添加“Cc:stable@kernel.org”; 当你的补丁进入主干时,这样能够让维护稳定版本的团队收到通知。

当为补丁选择收件人的时候,最好是知道谁最终会接受补丁并将补丁合并。虽然任何人都可以直接向 Linus Torvalds 发送补丁并让他合并,但人们一般不会这样做。Linus 很忙,相应的子系统一般都有专门的维护者,通常是由这些维护者来合并代码的,如果某块没有特别指定维护者,那么 Andrew Morton 就会担任起相应的角色来。

补丁需要描述良好的主题。补丁行的规范格式如下:

[PATCH nn/mm] subsys: one-line description of the patch

其中“nn”是补丁的序号,“mm”是系列中的补丁总数,以及“subsys”是补丁所涉及到子系统的名称。显而易见,单个独立补丁可以省略 nn / mm。

如果你有一系列重要的补丁,实际上首先发送的部分是介绍性的描述,然而,这只是一个大家默认遵循的准则,并不是强制执行,如果开发者使用了这样的形式,则一定要记住,引言中的信息是不会进入内核代码的。因此,请确保补丁本身具有完整的更改日志信息。

通常,多部分补丁的第二部分和后续部分应作为对第一部分的回复发送,以便它们在接收端收到的是一个整体补丁。Git、guilt 均支持命令行将补丁发送邮件,如果遇到的补丁是一个很长的系列,那么使用git --no-chain-reply-to 来避免创建特别深的嵌套。

六、获得通过

到了这步,作为内核的开发者,你已经经历了重重“磨难”:遵循了内核给出的指导原则、锻炼了自己的工程技能、甚至是已经发出过一系列完美的补丁了。但是还有一个容易被人们忽略的问题,那就是,想当然的以为工作就完成了,这甚至是很多经验丰富的人都会犯的错误。

对于补丁来说,一次就达到非常完美的境地,这是非常罕见的事情。内核的开发人员是深刻明白这样一个事实的,所以,大伙都是面向发布代码的改进这个基本前提的。作为代码的作者,需要与内核社区进行紧密的合作,以确保代码符合内核的质量标准。如果没有参与到这个过程的话,那么有非常的大的可能是补丁根本进不了主干。

审核工作

任何有意义的补丁都会在审核代码时产生其他开发人员的一些评论。对于许多开发人员来说,与审阅者合作可能是内核开发过程中最令人生畏的部分。但是,如果你记住以下几点,那么过程将变得容易一些:

  • 如果开发者已经很好地解释了自己的补丁,那么审核的人就会较为容易地理解该补丁的价值以及开发者为什么要这么写。但是这个价值并不能阻止他们提出一个基本问题:在五年或十年之后用这个代码维护内核是什么样的?开发者可能会被要求做出许多改变 —— 从编码风格调整到重大的调整 —— 都源于这样一种理解,即从现在起十年后Linux仍将处于开发阶段。
  • 代码审查是一项蛮困难的工作,而且是可能是一个相对吃力不讨好的活计;人们记住是谁编写了内核代码,但对于那些审阅内核代码的人而言,通常是被遗忘的,因此,审核的开发者可能会变得情绪化严重,尤其是当他们看到同样的错误一再发生时。如果你得到一个看起来很愤怒,侮辱或彻底冒犯的回复时,要适当的从对方的视角来理解一下,代码审查是基于代码而言的,这些人通常不是针对个人,代码审核者们是不会无缘无故攻击某人开发者的。
  • 同样,代码审查人员也不会以牺牲自己的利益为代价来推广雇主的补丁。内核开发人员通常希望从现在开始在内核上工作,但他们知道他们的雇主可能会改变。它们几乎毫无例外地致力于创造最好的内核;他们并没有试图为雇主的竞争对手造成不适。

以上所有,都可归结为,当内核审核者回复了你的补丁时,那么最应该注意的是他们对你正在做的事情的一个技术鉴定。不要让他们的表达形式或你自己的骄傲阻止这种情况发生。当收到有关补丁的评论意见时,请花时间了解评论者试图说的内容。如果可能,请修复审阅者要求修复的内容。并回复评论者:感谢他们,并描述你将如何响应他们的问题的。

不过也请注意,没有必要对审核者的每个建议都言听计从,审核者也会有误解他人代码的时候,那么这时要做的就是做出更多的解释。如果开发者对审核者所建议的更改有异议,那么就进行针对性的描述并要身体力行的证明解决问题的方法。如果开发者的解释是有实际意义的,那么审核者一般就会同意。但是,如果你的解释不具有说服力,特别是如果其他人开始同意审稿人,那么请花点时间再思考一下。你可以很容易地通过自己的问题解决方案使自己变得盲目,以至于你没有意识到某些事情是根本错误的,又或者是你根本就没有解决正确的问题。

对于别人的回复,一个致命的错误就是忽略。有问题的补丁,不会因为你忽略回复就被认为是可以行的通的,如果你再重新发布代码而没有回复之前的评论,那么你可能会发现自己的补丁无处可去。

说到重新发布代码:请记住,代码审核者不会记住你上次发布的代码的所有详细信息。因此,提醒评论者以前提出的问题以及如何处理这些问题总是一个好主意;补丁更改日志是这类信息的好地方。代码审核者不会也不应该去搜索历史档案以回忆上次所说的内容;如果你帮助他们获得一个好的开始,他们在重新访问你的代码时会有更好的心情。

如果你已经尝试做正确的事情并且事情仍然没有取得进展怎么办?大多数技术上的分歧可以通过讨论来解决,但有时候只需要作者做出决定即可。如果你真的相信这个决定对你不利,那么你总是可以尝试吸引更高的权力。在撰写本文时,更高的权力倾向于 Andrew Morton,Andrew在内核开发社区中颇有声望,他经常能够解决一些看似无解的僵局。但是,能让Andrew看上的问题,不是很容易,所以不要把找到Andew作为第一方案。而且还有很大的可能,Andrew也不会同意的看法。

接下来干什么

如果补丁被认为是正确无误的,可以被添加到内核中了,而且大多数的审核问题也得到了解决,下一步通常就是进入子系统维护者的代码树中,它具体的工作情况因子系统的不同而异,每个子系统都有自己做事情的方式,更为特殊的情况是,还拥有多个代码树,如一个用于专为下一代合并窗口计划的补丁,另外一个用户长期的维护。

对于应用于没有明显子系统代码树的补丁(例如,内存管理补丁),默认的代码树通常为 -mm,影响多个子系统的补丁也最终会通过-mm代码树。

包含在子系统代码树中可以让补丁为更多的开发者们看到,现在,使用该树的其他开发人员将默认获得补丁。子系统代码树通常也会输入-mmlinux-next,从而使整个开发社区可以看到它们的内容。在这一点上,您很有可能从一组新的评论者那里获得更多评论;这些评论需要像上一轮那样回答。

根据补丁的性质,此时可能还会发生与其他人正在进行的工作发生冲突的情况。在最糟糕的情况下,严重的补丁冲突可能导致一些工作被放在后面,以便剩余的补丁可以处理成形并合并。其他时候,解决冲突将涉及与其他开发人员合作,并可能在树之间移动一些补丁以确保一切都干净利落。这项工作可能会令人不爽,但是也要耐心搞定:在linux-next 树出现之前,这些冲突通常只在合并窗口期间出现,并且必须在这段时间内解决。现在,在合并窗口打开之前,它也可以在平时解决。

如果一切顺利的话,某天的清晨,你可能就会看到自己的补丁进入到了主干,这可是值得庆贺的事情啊!一旦庆祝活动完成(并且您已将自己添加到MAINTAINERS文件中),但是,值得记住一个重要的小事实:工作仍未完成。进入主干也意味着真正挑战的来临。

首先,补丁的可见性进一步提高。可能会有一些过去没有注意到的开发者开始对补丁进行评头论足,忽略它们可能很诱人,因为你的代码不再存在合并的问题。但是,要抵制这种诱惑;你仍然需要对有疑问或建议的开发人员做出回应。

更为重要的情况是,你的代码被包含在主干中,意味着拥有大量的测试着对你的代码进行测试,即使你是为一个原来尚未提供的硬件提供了驱动程序,你仍然会惊讶于有多少人将代码构建到其内核中。当然,有测试人员的地方就会有bug报告。

最糟糕的 bug 报告是回归。如果你的补丁导致回归,你就会发现有很多人会盯着你,回归的问题需要被尽快的修复。如果您不愿意或无法修复回归(也没有其他人帮助你),那么你的补丁注定是要被删除的,除了否定你为补丁进入主干所做的所有工作之外,由于未能修复回归而导致补丁被删除,这可能会使你在将来合并的工作变得更加困难。

在处理任何回归之后,可能还有其他普通的bug需要处理。稳定期是修复这些错误的最佳机会,并确保您的代码在主干内核版本中的首次亮相并尽可能的稳固。所以,请回答错误报告,并尽可能解决问题。这就是稳定期的目的;一旦旧的问题得到解决,你就可以开始创建更酷的新补丁了。

并且不要忘记还有其他里程碑也可能会创建错误报告:下一个主干稳定版本、知名的Linux发行版等等,继续回应这些报告是内核开发者工作中的基本工作。但是,如果动机不足,那么开发社区还是会记得在合并之后对代码失去兴趣的开发人员的。下次你发布补丁时,他们会评估它,并假设你以后不会去维护补丁。

其他可能发生的事情

或许有一天,你打开自己的邮件客户端,发现了有人给你发了一封关于代码补丁的邮件,请不要大惊小怪,毕竟,我们是在开源的世界里编码,你的代码是对所有人开放的,如果您任何该补丁,您可以将其转发给子系统维护者(确保包含一个正确的From行:告诉他没有问题,请添加自己的签名),亦或者是发送一个 acked-by: 回复,并告诉原始的发送人可以往更上一级发送。

如果你对补丁有疑问,请礼貌的回答并解释具体的原因,如果可能,请告诉作者需要做出哪些更改才能使你接受该补丁。合并补丁可能会遇到阻力,这是开发者和维护者之间固有的张力,但也就那么回事,就事论事就好,如果你是故意的针对一个优秀的补丁,那么这个补丁一定会以其它的方式进入到主干的,那时你就尴尬了,在Linux内核,没有人对任何代码拥有绝对的否决权。除了Linus。

在极少数情况下,您可能会看到完全不同的东西:另外一个开发者针对同一个问题提供了和你完全不一样的解决办法,很明显,这两个补丁只能有一个被合并,而且“我的优先”并不认为是一个值得探讨的论点,如果其他人的补丁取代你的并进入主干,那就是真的只有一种回应:很高兴你的问题已经得到解决,请继续工作。把一个人的工作以这样的方式拒绝可能是让人伤心的、令人沮丧的,但是社区会记住你的,即使在过一段时间之后已经没有人在乎谁的补丁被合并了。

七、高级主题

看官看到这里,笔者希望你已经掌握内核开发工作的基本原理。当然,学无止境,笔者在这一节当中会介绍一些高级的主题,对于希望成为一名Linux内核开发者来说相当的实用。

使用 git 来管理补丁

内核使用分布式的版本控制系统最早始于2002年,那是 Linus 第一次接触了专有软件 BitKeeper 应用程序,尽管 BitKeeper 在软件的商业形态上对大家来说争议颇大,但是就版本管理来说对于Linux内核还是非常有帮助的,使用了这个分布式版本控制之后,效果非常明显,立即加速了内核的开发进度。当然,后来的故事大家都知道了,现在已经有多个分布式版本控制系统够可以替代 BitKeeper。无论如何,BitKeeper 在linux的历史上起到了很为关键的作用,尽管现在已经彻底转到了 Git 上,Linus 另外创造的项目。

使用gitit管理补丁程序可以使开发人员的工作更轻松,特别是随着补丁程序的数量上不断增多。Git 尽管现在还不足够完善,但是相信它是一个潜力无限的项目,要相信开源的力量。另外需要说明的是,本文档不会为读者具体的讲解git的如何使用,讲解Git方面的资料有很多。相反,本文档针对内核开发的流程中Git的一些使用。开发者如果是找Git方面的资料的话,我们这里给出两个站点链接:

更多材料,请自行搜索。

写上面这一段内容的意思是希望明确如何使用git来制作补丁,进而让其他人可以看到,一位使用git 的开发者需要遵循一些固定的内容:

  • 内核主干仓库的副本
  • 浏览版本的历史
  • 查看提交到分支的变更
  • 使用分支
  • …..等等

进一步深入git的开发者,可能还会做一些诸如重写历史(rebase)之类的动作。Git 拥有属于自身的术语和概念,一位git的入门用户首先要理解的如refs、远程分支、索引、fast-forward 合并、push、Pull、分离的头等等,这些名词和动作看起来有点和平时不一样的,稀奇古怪的,但是稍微学习一下,他们其实是蛮好懂的。

使用 git 来生成补丁,并通过email来提交,是提升效率的最佳实践。

一旦当开发者准备好将一个Git库给他人查看的时候,当然,首先是需要一个服务让他人能够去pull,如果开发者本来就有一个可以让互联网访问的服务器的话,那么使用git-daemon来配置服务是蛮容易的,另外,也可以选择一些免费的、公共托管的网站服务(如GitHub),目前的内核开发人员在kernel.org上均有自己的账号,初来乍到的开发者获得这个账号是需要花一些时间的,更多信息可以参考:http://kernel.org/faq/

正常的git流程一般均会涉及到大量的分支,每条开发线都可以分成单独的“主题分支”并独立维护。在 git中使用分支的代价非常低,没有理由不去好好的运用它,而且,在任何情况下,都不要去让别人去pull你正在开发的分支。应谨慎创建公开的分支;合并来自开发分支的补丁,是那些已经处于完整形式而且准备好了 ———— 而不是还出开发当中。

Git 提供了允许开发者重写开发历史的强大工具,(俗语有云:人生有悔,Git没有)一个糟糕的补丁(诸如破坏bisection,或者说有一些其它类型的明显错误的补丁)可以随时修复,或者干脆直接从历史中消失,一系列的补丁都是可以被重写的,即使是几个月前写的内容,也可以出现在最近的提交之上,变更可以从一个分支完全透明的转移到另外一个分支,等等。明智的使用 git 修改历史记录的能力,可以让仓库更为的清晰。

但是,过犹不及,即使Git拥有如此强大的功能,也不应在一个仓库中过度频繁的使用,这可能会导致其它问题的出现,要知道,创建项目真正的目的并不是撰写完美的历史描述。重写历史记录也意味着要重写该历史记录中的更改,而那些已经经过测试的内核代码就会变为未经测试的内核代码。但是,除此之外,如果开发人员没有项目历史的共享视图,他们就无法轻松进行协作;如果你重写了其他开发人员已经进入内核的历史记录,那么你将给他们带来大量额外的艰辛的工作。所以这里有一个简单的经验法则:给到他人的历史通常应该被视为是不可变更的。

所以,一旦将更改的内容推送到了公共的仓库,那么这些更改就不应该被重写了。如果开发人员尝试推送不会导致fast-forward合并的更改(如,没有相同历史的更改),Git会尝试强制执行此规则。可以覆盖此检查,有时可能需要重写导出的树。在代码树之间移动变更集以避免在linux-next中的冲突就是一个例子。但这种行为应该很少见。这是开发应该在私有分支中完成的原因之一(必要时可以重写),并且只有在处于高度完善状态时方可入公共分支。

由于主干(或者一组有其它分支的基础仓库)是先进的,因此和这些代码树合并后是非常超前的,对于私有仓库来说,rebase 可以很容易的实现和其它代码树保持一致,但是,一旦代码树抛到了公开的仓库,则rebase就显得不那么好用了。而假如非要去做的话,那么就必须进行完全的合并。偶尔的合并还可以接受,但是过于频繁的合并则会带来不必要的混乱。在这种情况下,建议是不要进行经常合并,通常只在特定的发布点(例如主干-rc发布)合并。如果您对特定的更改比较敏感,可以始终在专用分支中执行测试合并。git rerere工具在这种情况下很有用;它记得如何解决合并冲突,这样你就不必两次做同样的工作了。

关于像git这样的工具最大的反复出现的抱怨之一是:从一个仓库迁移到另外一个仓库会很容易出现 slip in ill-advised的变更,而这些是又要重新置为待审核状态的,而内核人员看到这样的情形时,是非常不爽的,使用未审查或偏离主题的补丁放置一个 git 树会影响你将来获取更新的代码的能力。

Linus 对于这样的情况有过非常精彩的叙述:

你发送给我一个补丁,而我要将你的补丁pull到主干,我需要知道你知道自己在干什么,而且我需要去信任你,而不是亲自动手去做所有的检查。 ———— 摘自 http://lwn.net/Articles/224135/

要避免类似的情况发生,请确保给定分支中所有的补丁都和相关主题紧密关联,一个“驱动程序修复”的补丁不应该和内存管理的补丁纠缠在一起,而且,最重要的是,不要使用git树来尝试绕过审核过程。请将临时的树提交到相关的列表中,然后当时机成熟时,然后将该树包含在linux-next树中。

如果有其他开发者发送补丁到你的代码树中,请记得去审核它们,还要确保您保留正确的作者信息;git am工具可以将该工作完美的完成,但如果通过第三方转发给您,您可能需要在补丁中添加”From:“行。

当去Pull代码的时候,请务必提供所有相关信息:你的代码树来自哪里、要拉那个分支、以及拉下来要做什么事情等,git request-pull 工具可以做这件事,它将像其他开发人员期望的那样来格式化请求,并且还将检查以确保已记住将这些更改,以推送到公共服务器。

审核补丁

可能会有一些读者将这部分的内容放在所谓的“高级主题”下有一定的非议,大概是因为即使是初级的内核开发者,也应该学会审核代码!毫无疑问,学习如何在内核环境中编程,没有比通过查看其他人发布的代码更好的方法,另外,代码审核者永远供不应求;通过查看代码,可以对整个流程做出重大贡献。

审查代码可能是一个让人心生畏惧的流程,尤其是一名初来乍到的内核开发者,在公共场合被提问,可能会感到不自在,尽管代码有可能是由更有经验的人所发布的。但是,要知道,即使是经验最丰富的开发人员所编写的代码,也可能有很大的改进空间。对于评论者(所有评论者)而言,最好的建议可能是:以问题回复的短评好过于批评。举例来说,“在该路径中所是如何释放的?”这样的询问,要好过于:“在这里写所是错误的做法。”

不同的开发者对于代码有着不同的视角。一些人主要关注编码风格以及代码行是否具有尾随空格,有些人则主要关注整个补丁实现的更改是否对内核有利,还有人会去检查是否存在问题锁定、过多的堆栈使用、可能的安全问题、在其他地方找到重复的代码、文档欠缺、性能的负面影响、用户空间ABI更改等等。

所有类型的代码审核,如果它们导致更好的代码进入内核,就是受欢迎的,是值得去倡导的。

八、更多帮助内容

内核本身也有许多关于 Linux 内核开发和相关主题的信息。其中首先是内核源代码分发中的 Documentation 目录。顶级的 HOWTO 文件也是一个不错的开始;SubmittingPatchesSubmittingDrivers 也是所有内核开发人员都应该阅读的内容。使用 kerneldoc 机制记录了许多内部内核API; “make htmldocs”“make pdfdocs” 可用于生成HTML或PDF格式的文档(尽管某些发行版附带的TeX版本会遇到内部限制并且无法正确处理文档)。

也有多家网站分别撰写不同层次上的内核开发知识,本文作者强烈推荐站点之一:http://lwn.net/,有关许多特定内核主题的信息可以通过LWN内核索引找到:http://lwn.net/Kernel/Index/

除此之外,再推荐一个内核开发人员很有价值的站点:http://kernelnewbies.org/

有关linux-next树的信息聚集在:http://linux.f-seidel.de/linux-next/pmwiki/

当然,我们不应该忘记 http://kernel.org/,这里是内核发布信息的最终位置。

关于Linux内核开发方面的书籍,其实也蛮多:

  • Linux Device Drivers, 3rd Edition (Jonathan Corbet, Alessandro Rubini, 以及 Greg Kroah-Hartman)。在线版:http://lwn.net/Kernel/LDD3/
  • Linux Kernel Development (Robert Love)。
  • Understanding the Linux Kernel (Danial Bovet 和 Marco Cesati).

以上这几本书都有一个共同的问题,即当这些书在市面上出现时,往往就有点过时了。但是对于你的学习仍然具有非凡的价值。

关于 Git 方面的文档,这里也推荐两个站点:

总结

恭喜你,能读到这里说明你是一位有极大耐心的开发者,不过我们还是真心的希望这份文档对于你来说起到很大的作用,即能够让你理解Linux内核的开发过程,并期望你参与进来。

说了这么多,其实最重要的是,以实际行动来回报这一切:重在参与。任何项目都是每一位独立的贡献者共同努力的结果。Linux内核之所有拥有如此快速的发展,是因为它获得了大量的用户,以及开发者的青睐,正式因为他们才使得Linux有今天的成绩。在人类历史上,Linux内核是重要的如此之多的人们协作完成的伟大工程。

不过,内核总是可以从更大的开发人员基础中受益。总有更多工作要做。但是,同样重要的是,Linux生态系统中的大多数其他参与者都可以通过为内核做出贡献而受益。将代码放入主干是提高代码质量,降低维护费用和发行的成本,对内核开发方向的产生更高影响力等等的关键。这是一个所有参与者均有收获的情形,打开你的编辑器,加入我们吧!我们非常欢迎你的加入!

原文链接

How to Participate in the Linux Community,作者 JONATHAN CORBET


2019.4.24 发布公众号(下)