2012年三月月 发布的文章

如何加入Linux内核开发社区(4)

本文翻译自The Linux Foundation的《How to Participate in the Linux Community》(基于2012-03-21最新版本),原作者为Jonathan Corbet(corbet@lwn.net)。 下面是该文章第四章节的中译文。

 
4、正确地编写代码
 
关于那个可靠的面向社区的设计过程我们已经说的够多了,任何内核开发项目的证据都是最终的代码。被其他开发者检查的是代码,被(或没有被)合并到主线树的也是代码。因此是代码质量决定了内核开发项目最终的成功。
 
这一节我们会对内核编码过程进行剖析。我们会首先看看内核开发者可能会出错的几个方面;接下来我们会将关注点转向如何正确地做事以及一些可以在此过程中帮助到我们的工具。
 
4.1、陷阱
 
* 编码风格
 
内核早已拥有了一套标准的编码风格,在Documentation/CodingStyle文件中有关于编码风格的说明。但长久以来,这个文档中所描述的风格策略充其量被视为是建议性的。因此内核中有大量的代码并不符合编码风格准则的要求。这类代码的存在给内核开发者设下了两个陷阱。
 
第一个陷阱是相信内核编码标准无关紧要并且不是强制性的。而这事儿的真实情况是如果代码没有按照标准编写,新代码将很难被添加到内核中去;许多开发者会要求代码应该在评审之前被重新格式化。像内核这么大规模的代码库需要一种格式一致的代码,这样才能保证开发人员可以快速地理解代码库中的任何一部分。因此这里没有给奇怪格式代码生存的空间。
 
有时,内核编码风格会与某个雇佣者要求的风格相冲突。这种情况下,在代码可以被合并到内核之前,内核编码风格将取得胜利。将代码放入内核意味着你将在多个方面放弃一些对代码的控制,这其中就包括对代码风格的控制。
 
另外一个陷阱是假定那些已经存在于内核中代码急需修复代码风格。开发者在开始阶段很可能会将创建修复代码风格的补丁作为一种熟悉开发过程的手段,或作为一种将自己的名字写进内核Changelog文件的手段,或者二者兼具。但纯粹的代码风格修复补丁会被开发社区视为噪音;并很可能会被冷眼对待。因此最好杜绝这类补丁。比较自然合理的做法是在因其他原因修改某段代码时顺便修复其代码风格,不要为了自身的考虑而去修改代码风格。
 
这个代码风格文档也不应该被当成绝对不能违背的准则。如果你有违反这一风格的好理由(例如,某一行如果按照80列的限制做拆分,可读性就会变得很差),那就按照你的想法去做吧。
 
* 抽象层
 
计算机科学教授教育学生应广泛使用抽象层以实现系统的灵活性和信息隐藏。当然内核也广泛使用了抽象;如果不这样的话,没有哪个具有百万行代码的项目可以实现并存活下来。但经验证明过度或过早抽象可能同过早优化一样是有害的。抽象应该被用在需要的层次上并且不要再深入了。
 
在一个简单的层次上,考虑这样一个只有一个参数的函数,调用者在调用该函数时参数总是传0。只是在有人最终需要使用这个函数提供的额外的灵活性时人们才会记起那个参数。但是到了那时,很可能实现了这个额外参数的代码已经被一种未被察觉的微妙方式改变了– 因为它从未被使用过。或者,当对这个额外灵活性的需求增多时,它的行为已经不再是开发者早先期望的那样的了。核心开发者将会按惯例提交补丁删除无用的参数;通常来讲,这些参数从一开始就不应该加上。   
 
那些隐藏了对硬件访问的抽象层尤其不被赞成使用,因为这些抽象层常常允许一个驱动程序的主要部分被多个操作系统使用。这些抽象层使代码更加难以理解并且很可能引入性能问题;他们不应该被归入Linux内核范畴。
 
另一方面,如果你发现自己正在从另一个内核子系统拷贝大量重要代码,那么是时候问问你自己将一些代码抽出放入单独的库或在更高层次上实现那个功能是不是更有意义。在内核内部复制相同的代码没有价值。
 
* #ifdef以及预处理器的一般使用
 
C预处理器对一些C程序员来说是一种强大的诱惑,这些C程序员将预处理器看作是一种在源文件中嵌入灵活性的手段。但是预处理器不是C语言,过度地使用预处理器将导致代码可读性大大降低,同时也使得编译器进行正确性检查的难度大大增加了。过度使用预处理器通常是一种信号,预示着代码需要一些整理了。
 
采用#ifdef的条件编译确实是一种强大的特性,并且它已经被用于内核代码中。但我们仍然不希望看到那些不受限制地使用#ifdef代码块的代码。一般来说,#ifdef应该尽可能地被限制在头文件中使用。条件编译代码可用于那些尚未实现完毕的函数,使之变为空函数。编译器接下来会在优化过程去掉对这些空函数的调用。结果我们将得到更加简洁和易理解的代码。
 
C预处理器宏会带来一些危害,包括可能带有副作用的多次表达式求值以及类型不安全。如果你总想定义一个宏,不妨考虑创建一个内联函数替代这个宏,两者的执行结果是相同的,但内联函数可读性更好,也不会多次对其参数进行求值,并且支持编译器对参数以及返回值进行的类型检查。
 
* 内联函数
 
不过内联函数也有他的一个危害之处。程序员们可能会迷恋于因省略函数调用而带来的效率提升,并在源文件中到处使用内联函数。然而那些函数实际上可能降低系统性能。由于在每个调用处这些函数的代码都会被复制一份,最终会导致编译后的内核尺寸膨胀。相应地,这会给处理器的内存缓存带来压力,并可能显著降低执行性能。通常,内联函数应该非常小并且相对较少。毕竟函数调用的消耗并不是那么大;大量创建内联函数是过早优化的一个典型例子。
 
一般来说,内核程序员忽略缓存效果是冒着风险的。在数据结构课程上学到经典的时空开销转换并不适用于当代的硬件。空间即是时间,因为尺寸更大的程序与更加紧凑的程序运行的要更慢。
 
* 锁
 
2006年5月,Devicescape网络协议栈在GPL的授权下大张旗鼓地发布了,并且等待着被主线内核合并。这次捐赠受到了社区的极大欢迎;因为当时Linux对无线网络的支持被认为是不符合标准的,而Devicescape协议栈则许诺修复这一问题。但直到2007年6月份(2.6.22),这份代码也没有被真正合并到内核主线中。究竟发生了什么呢?
 
这份代码显现出诸多闭门造车的迹象。然而一个更为严重的问题是它不是针对多处理器系统设计的。在这份网络协议栈(现在叫作mac80211)能够被合入主线之前,社区需要一个锁方案来重新对该代码进行改造。
 
曾几何时,Linux内核代码的开发可以无需考虑多处理器系统的并发问题。不多,现在,就连这篇文章也是在一个双核处理器笔记本上编写的。即使在单处理器系统上,那些为了改善响应速度的工作也会提升并发在内核内部的级别。那些无需考虑锁的内核编码的日子已经一去不复返了。
 
任何可被不止一个线程并发访问的资源(数据结构、硬件寄存器等)都必须用锁保护起来。开发新代码时应牢记这一要求;即成事实后再进行锁改造将会是一个特别困难的任务。内核开发者应该花时间去好好地了解一下已经存在的锁原语以足够自己为开发任务挑选一个合适的工具。那些缺少对并发关注的代码在通往内核主线的道路上会走得更加艰难。
 
*Regressions(退步)
 
最后一个值得一提的危害是:作出一些给现有用户带来破坏的改变(可能带来较大的改进)。这类改变被称作"regression(退步)",内核主线最厌恶regression。如果regression不能在短时间内修复,那些导致regression的改变将极少例外地被清退出内核。最好从一开始就避免regression。
 
如果因某个regression所带来的改变而受益的人比因其受害的人更多,这个regression是否可能被合法化呢?这里常常引发社区的争论。为什么不可以做出这样一个改变呢:它能给10个系统带来新功能,但只破坏其中一个系统?对于这个问题,Linus在2007年7月给出的最佳答案:
 
所以,我们不能通过引入新问题的方式来修复bug。那种方式很愚蠢,根本没有人知道你实际上是否带来的真正的进步?是前进两步,后退一步,还是前进一步后退两步呢?(http://lwn.net/Articles/243460/).
 
一个尤其让人生厌的regression是那种对用户空间ABI(译注:Application Binary Interface,应用程序二进制接口)的改变。一旦一个接口被导出到用户空间,它就必须被无限期地支持。这种情况让创建用户空间接口变得尤其具有挑战性:因为它们不能被以一种不兼容的方式改变,它们必须在一开始时就被正确地创建。为此,用户空间接口总是需要大量的考量、清晰的文档以及大范围的评审。
 
4.2、代码检查工具
 
至少在目前,编写无错代码仍旧是一个几乎无人可及的理想。然而,我们可以期望的是,在代码进入内核主线之前,尽可能多的捕捉和修复bug。为达到此目的,内核开发者们设计和实现了一系列工具,这些工具可以自动地捕捉到各种隐蔽的问题。被计算机捕捉到的问题后续将不会折磨用户,因此,顺理成章,我们应该尽可能多地使用这些自动化工具。
 
第一步就是留心编译器给出的警告。当前版本的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必须单独安装(如果你用的发行版不包含sparse的话,你可以在http://www.kernel.org/pub/software/devel/sparse/下面找到它);当你执行的make命令包含"C=1"时,sparse会被执行。
 
其他有关可移植性类别的错误最好在代码进行针对其他体系的编译时发现,如果手头没有S/390系统或Blackfin开发板的话,你仍然能够执行这个编译步骤。一套适合x86系统的跨平台编译器可以在下面页面中找到:
 
http://www.kernel.org/pub/tools/crosstool/
 
花些时间安装和使用这些编译器可以帮助你避免日后难堪。
 
4.3、文档
 
文档常常不仅仅是内核开发规则的例外。即使这样,充足的文档会有助于你的新代码合并入内核,有助于其他开发人员理解你的代码并且也会对你的用户带来帮助。在很多情况下,增加文档已经变成了必不可少的强制要求了。
 
任何补丁的文档的第一部分内容应该是与之相关的变更日志(changelog)。日志记录应该描述解决了什么问题、解决方案的构成、补丁相关的人员、任何对性能产生的影响以及其他理解该补丁所需要的内容。
 
任何添加了新用户空间接口–包括新的sysfs或/proc文件–的代码都应该包含一份关于那个接口的说明文档,以便用户空间程序员了解这个接口。关于这类文档应该如何进行格式化以及应该提供哪些信息,请参见Documentation/ABI/README。
 
Documentation/kernel-parameters.txt描述了内核引导阶段的所有参数。任何添加新参数的补丁都应该在该文档中添加适当的记录。
 
任何新增的配置选项都必须伴随一份帮助文字,这些文字应该清楚地说明这些选项的功用以及用户何时可能会对它们进行选择。
 
在多个子系统中使用的内部API信息需要以一种特定格式的注释的方式记录到文档中;这些注释可以被"kernel-doc"脚本以多种方式提取和格式化。如果你正在一个具有kerneldoc注释的子系统上进行开发,你应该视具体情况为外部可用的函数维护和添加注释。即使在尚没有文档记录的区域,为将来添加kerneldoc注释也是无害的;实际上,对于那些刚进入内核开发领域的开发者来说,这可能是一种有益的工作。关于这些注释的格式以及如何创建kerneldoc模板的说明可以参见Documentation/kernel-doc-nano-HOWTO.txt。
 
读过大量现有内核代码的人常常都会注意到内核代码严重缺少注释。对新代码中注释的期望远远高于之前的代码;没有注释的代码想要合入内核会更加困难。但即便如此,那些具有冗长注释的代码想进入内核依旧是希望渺茫。代码自身应该具有良好的可读性,同时使用注释解释那些不明显、更具技巧的特性。
 
某些地方应该始终使用注释。内存栅栏(memory barrier)的使用应该始终伴随一行注释,解释这里使用栅栏的必要性。数据结构的加锁规则一般需要在某处给予解释。通常主要的数据结构都需要详细的文档。小块代码间的不明显的依赖需要被指出。任何可能诱使一个代码看门人(code janitor)作出不合规矩地"清理"的地方都需要一个注释解释为何这里要这么做。等等。
 
4.4、内部API变化
 
除非是最为严重的情况下,内核提供给用户空间的二进制接口都不能被破坏。相反,内核内部的编程接口则是经常改变的,并且可以在有需要的情况下被改变。如果你发现自己围绕着一个内核API进行开发或只是没有使用一个特定的功能,因为该功能无法满足你的需要,这很可能是一个API需要被改变的信号。作为一个内核开发者,你有权做出这样的改变。
 
当然,这里还是有一些隐患的。API可以被改变,但这种改变应该是合理的。因此任何导致一个内部API变化的补丁都应该伴随一个描述,该描述包括改变了什么以及这种改变的必要性。这类改变还应该被拆分成多个独立的补丁,而不是放在一个大补丁中。
 
另外一个隐患是改变内部API的那个开发者通常还要负责修正内核树上那些因API改变而被破坏的代码。对于一个被广泛使用的函数来说,这个责任可能会意味着成百或上千处改变– 多数都可能是与其他开发者所编写的代码的冲突。不用说,这也是一个工作量庞大的工作,因此最好确认你对API改变的合理性是可靠的。
 
当做出一个不兼容的API改变时,开发者应该尽快能的保证编译器可以捕捉到那些尚未更新的代码。这将有助于你在树内找到所有使用这个接口的代码。它还会警告那些树外代码的开发者有一个需要他们处理的新变化。虽然树外代码的支持不是内核开发者需要担心的事情,但我们还是不要让树外代码的开发者的开发工作变得更难。 

如何加入Linux内核开发社区(3)

本文翻译自The Linux Foundation的《How to Participate in the Linux Community》(基于2012-03-21最新版本),原作者为Jonathan Corbet(corbet@lwn.net)。 下面是该文章第三章节的中译文。

3、早期规划

当考虑一个Linux内核开发项目时,人们可能很想尽快投入并开始编码。但和任何重要的项目一样,推动项目成功的大量基础工作需要在第一行代码编写之前被精心安排好。一些在早期计划和沟通阶段所花费的时间可能在后期为你节约更多的时间。

3.1、明确问题

和任何工程项目一样,一次成功的内核改进都始于对所要解决问题的清晰描述。在某些情况下,这一步很简单:例如,当需要一块特定硬件的驱动程序时。但在其他情况下,人们很可能把真正的问题与提议的解决方法混为一谈,这将带来更多麻烦和困难。

考虑一个例子:几年前,Linux音频开发者试图找到一种方式使得音频应用在运行时不会因系统过分延迟而导致丢帧或其他人造干扰。在他们给出的解决方案 中,他们打算在Linux Security Module (LSM)框架中挂接一个模块;这个模块可以配置某特定应用是否具有访问实时调度器的权限。他们将实现后的模块发布到linux-kernel邮件列表 中,瞬即就遇到了问题。

对于这些音频开发者来说,安全模块(security module)足以可以解决他们目前遇到的问题。但对于更广大的内核开发社区而言,这却是一种对LSM框架(该模块不是用来给那些本就不会具有权限的进程 授权的)的误用,同时对系统稳定也是个风险。因此社区开发者们的首选方案是短期内通过rlimit机制访问实时调度,并将减少延迟作为长期工作。

然而,音频社区不愿放弃他们已经实现的方案,他们不愿接受其他选择。由此导致的分歧让这些开发者不再对整个内核开发过程抱有幻想;其中一个开发者回到audio邮件列表并发表了下面这段话:

这里的确有很多优秀的Linux内核开发者,但他们往往是一群大声喊叫的傲慢自大的傻瓜。和这些人沟通用户需求简直就是浪费时间。他们都太过聪明,根本听不进去凡人的建议。

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

现实的情况却不是这样的;与一个特定的模块相比,内核开发者们更加关心系统的稳定性、长期维护以及找到问题的正确解决方案。这个故事的寓意是把重点放在问题上,而不是某个特定的方案,并且在实现方案前与开发社区进行充分的讨论。

因此,当考虑一个内核开发项目时,每个开发者都应该首先得到下面几个问题的答案:

  * 要解决的问题到底是什么?
  * 这个问题究竟影响了哪些用户?这个方案到底解决了哪些用例?
  * 当前在解决这个问题上内核是如何无法达到要求的?

只有这样,开始考虑可能的方案才是有意义的。

3.2、早期讨论

当规划了一个内核开发项目时,在开始实现前保持与社区的充分讨论是十分有意义的。早期沟通可以从许多方面帮你节省时间和省去麻烦:

  * 内核很可能以你未曾听说过的方式解决问题。Linux内核规模巨大,有一些特性和能力并非是显而易见的。另外不是所有的内核能力都有完好的文档的,你很容 易错过一些事情。 笔者就曾经见到过有人提交的一个完整的驱动程序与已有的驱动程序重复了,并且这个新驱动程序的作者之前并不知道这个驱动程序已经有了。那些重新发明已有轮 子的代码不仅仅是浪费,而且它也不会被主线内核接受。

  * 提出的方案无法被主线接受也许有很多因素,最好在编写代码前先弄清楚此类问题。

  * 其他开发者完全有可能已经考虑过这个问题了;也许他们有更好的解决方案,并且可能愿意帮助你实现那个解决方案。

多年的内核开发社区经验清楚地告诫我们:通过闭门造车设计和开发的内核代码无疑例外都会有这样那样的问题,而这些问题只有在代码被发布到社区后才能被发现。有时,这些问题十分严重,需要几个月或几年努力才能达到内核社区的标准。下面是一些例子:

  * Devicescape网络协议栈只是针对单处理器系统设计和实现的。它无法被合并到主线版本,除非它适合多处理器系统。对这些代码做锁改造非常困难。结果,这份代码(现在称为mac80211)的合并工作推迟了一年多。

  * Reiser4文件系统包含了许多能力,但核心内核开发者认为这些能力本应该在虚拟文件系统层实现。它还包含了一些特性,但如果不将系统暴露给用户导致的 死锁,这些特性就无法轻易实现。后续发现的这些问题 — 以及作者拒绝解决其中一些问题 –导致了Reiser4依旧置身于内核主线之外。

  * AppArmor安全模块使用了内部虚拟文件系统的数据接口,这种方式被认为是不安全和不可靠的。虽然代码已经明显做过返工,但仍然被排除在主线之外。

在这些例子中,大量痛苦和多余的工作本可以通过早期与其他内核开发者的讨论而被避免。
   
3.3、你与谁讨论?

当开发者决定将他们的项目公开时,接下来的问题将是:我们从哪里开始?答案是找到正确的邮件列表(s)以及正确的维护者。对于邮件列表,最佳的方法就是在 MAINTAINERS文件中寻找一个相关的地方。如果存在一个合适的子系统邮件列表,在那里发布往往比在linux-kernel上发布要更好;你更有 可能碰到具备相关子系统专业知识的开发者以及更能给你提供支持的环境。

找到维护者可能更为困难些。这次,MAINTAINERS文件依旧可以作为寻找的起点,但该文件的更新不总是那么及时,并且不是所有子系统的维护者都会放 在那里。实际上,在MAINTAINERS文件中所列的维护者目前可能已经不再扮演维护者那个角色了。因此,当不知道应该联系谁时,一个实用的技巧是使用 git查看(尤其是"git log")谁是当前你所感兴趣的子系统库的积极开发者。看看谁在写补丁,谁在评审那些补丁。这些人将是给新开发项目带来帮助的最佳人选。

如果以上尝试都失败了,咨询Andrew Morton不失为一个有效的查找特定代码维护者的方法。

3.4、什么时候发布?

如果可能的话,在早期阶段发布你的计划是有帮助的。描述一下你的项目解决的问题以及如何进行实现的计划。你能提供的任何信息都可以帮助开发社区在此项目上提供有用的输入。

在此阶段发生的一个令人沮丧的事情不是怀有敌意的反应,而是少有反应或根本没有反应。这个事情的真实情况是(1)内核开发者都很忙;(2)拥有宏大计划但 几乎没有代码(或甚至是代码展望)的人有太多了,(3)没有人有义务去评审或评论其他人发表的想法。如果一个请求发表建议的mail没有收到几条建议,千 万不要以为没人对你的项目感兴趣。当然,你也不能假定你的想法就没有任何问题。这种情况下最好的做法是继续做下去,并将你做的事情持续通告给社区。

3.5、获得官方认可

如果你的工作是在公司环境下完成的–就像大多数Linux内核开发工作那样–显然你在将你公司的计划或代码发布到公共邮件列表之前应该先从适当的管理 者那获得授权。那些没有清楚地在GPL兼容许可证下发布的代码很可能是有问题的;公司的管理和法律人员越快同意发布这个内核开发项目,参与的人员才能更好 的脱离。

一些读者此刻可能会想到他们的内核开发工作是打算支持一个尚未正式承认存在的产品。在一个公共邮件列表上透露他们雇主的计划可能不是一个可行的方案。在这种情况下,值得考虑保密是否真的必要;实际上,常常并不是真的需要对开发计划进行保密。

不过,也有一些情况下,公司不能在其开发过程早期透露其开发计划。拥有丰富经验内核开发者的公司可能选择以开环的方式继续进行,前提是假设他们能够避免后 续很多严重的集成问题。对于那些没有专们内核开发经验的公司,常见的最佳选择是雇佣一个外部开发者,让其在不公开协议的约束下去评审开发计划。Linux 基金会运营了一个NDA计划,专门设计用于帮助此类情况;更多的信息参见:http://www.linuxfoundation.org/en/NDA_program

在不要求公开项目的情况下,这种评审对于避免后期出现的一些严重问题往往是足够的了。

如何加入Linux内核开发社区(2)

本文翻译自The Linux Foundation的《How to Participate in the Linux Community》(基于2012-03-21最新版本),原作者为Jonathan Corbet(corbet@lwn.net)。下面是该文章第二章节的中译文。

2、内核开发过程是如何进行的

在20世纪90年代初,当时的Linux内核开发是一件非常松散的事情,涉及的用户和开发人员数量也相对较少。但随着每年数以百万计的用户和大约2000 名开发者的参与,内核制订出了许多过程来保证开发工作的顺利平滑地进行。为了成为内核开发过程的一个有效部分,扎实地了解内核开发过程是如何进行的是十分 必要的。

2.1、整体情况

内核开发者使用一种松散的基于时间的发布(release)过程,每2到3个月发布一个新内核版本。近期的发布历史记录如下:

2.6.26    7月13日,2008
2.6.25    4月16日,2008
2.6.24    1月24日,2008
2.6.23    10月9日,2007
2.6.22    7月8日,2007
2.6.21    4月25日,2007
2.6.20    2月7日,2007

每个2.6.x发布版本都是一个主要的内核发布版,其中包含了新特性、内部API变化以及其他更多内容。一个典型的2.6发布可能包括超过10000个变 更以及几十万行代码的改变。因此2.6是Linux内核开发的前沿;内核采用一种滚动开发的模型,持续不断地将重大变化整合进来。

关于每个发行版的补丁合并,大家遵循一个相对简单直接的规则。在每轮内核开发周期的开始,我们说"合并窗口"是打开的。那时,那些被认为已经足够稳定(并 且被开发社区接受)的代码会被合入主线内核。在这期间,内核将以接近每天1000个变化("补丁"或"变更")的速度将一个新开发周期的大量改变(以及所 有重大的变化)合入主线内核。

(顺便说一句,这是值得注意的是合并窗口阶段整合的变化不是凭空而来的;它们早已被收集、测试和提交到阶段树中的。这个过程是如何进行的在后续会有详细介绍)。

合并窗口持续打开约两周时间。在这段时间的末尾,Linus Torvalds会宣布合并窗口关闭并发布这一轮内核版本开发的第一个"rc"版本(译注:Release Candidate,发布候选版)。例如,对于版本号确定为2.6.26的内核来说,合并窗口关闭后发布的版本将称为2.6.26-rc1。-rc1的发 布是一个信号,预示着合并新特性的阶段已经过去了,开发工作进入了稳定内核版本的阶段。

在接下来的6到10个星期里,只有那些修正问题的补丁才应该提交到主线中。偶尔也会有某个特别重要的变化被允许提交到主线,但这种情况极其少见;那些尝试 在合并窗口之外提交新特性的开发者往往会收到一个不友好的对待。作为一般规则,如果你的特性错过了合并窗口,那你最好是等待下一个开发周期。(一个不常见 的例外是那些用于之前不支持的硬件的驱动程序;如果它们没有触及到树内代码,那么它们就不可能导致回退(regression),在任何时候添加都应该是 安全的。)

随着越来越多的修复进入主线,补丁率将随着时间的推移而下降。Linus大约每周发布一个新的-rc内核版本;在内核被认为足够稳定且最终的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稳定版发布

开发人员是如何判断何时结束这一轮的开发周期并发布稳定版呢?他们采用的最重要的度量方法是上一个版本的regression列表。Bug虽然是不受欢迎 的,但那些存在于以前版本中的可导致系统崩溃的问题则被认为是更为严重的。因此,那些导致内核回退的补丁将遭受冷遇,并且非常可能在内核稳定化阶段被恢复 到原先状态。

开发者的目标是在稳定版内核发布之前修正所有已知的regressions。但在现实世界中,要想达成这种完美的目标十分困难;这种规模的项目存在太多的 变数。有某一点导致最终发布推迟就会使问题变得更加糟糕;等待下一个合并窗口的改变将会越来越多,并且会在下一个周期导致更多的regession出现。 因此,大多数2.6.x内核发布时只带有少了的已知regressions,然而,但愿这些regressions都不那么严重。

一旦稳定版内核发布,其后续的维护工作将交由"稳定版小组(stable team)"进行,目前这个小组成员包括Greg Kroah-Hartman和 Chris Wright。稳定版小组会不时地发布稳定版内核的更新版本,版本号采用2.6.x.y这种数字样式。如果想要被纳入更新版本,补丁必须(1)修正一个重 要的bug并且(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

一个已知内核的稳定版本的更新工作大约进行6个月左右;之后,稳定版的维护将由那些交付特定内核版本的发行商单独负责。

2.2、一个补丁的生命周期

补丁不会从开发者的键盘直接进入内核主线。相反,开发社区设计了一个稍显复杂的过程(虽然有些非正式)来保证每个补丁都能被评审以确保质量,且每个补丁都 实现了一个对内核主线有吸引力的改变。对于一些较小的修正来说,这个过程执行地很快,但对于较大的且有争议的改变来说,这个过程可能会持续数年。许多开发 人员所遭遇的挫折都是来自于缺乏对这一过程的理解或尝试绕开这一过程。

为了减少这类挫折,本文会详细说明补丁是如何进入到内核中的。接下来是一段关于这个过程的介绍,方式略有些理想化。更多详细的内容将在稍后的章节中给出。

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

  * 设计。这里将对补丁的真正的需求以及如何满足这些需求进行设计。设计工作常常在社区之外完成,但如果可能的话,最好公开地进行这个设计工作;这可以节省大量后期重设计的时间。

  * 早期评审。补丁被发布到相关的邮件列表,列表中的开发者就此回复评论。如果一切进展顺利,这个过程应该可以找出补丁中的重大问题。

  * 更宽泛的评审。当补丁越来越接近于被主线合并时,它会被一个相关子系统的维护者接受,但这种接受并不保证这个补丁一定会进入主线。这个补丁会出现在这个维 护者的子系统树中并且进入阶段树(前面提到过)中。这个过程会为补丁带来更为广泛的评审,并发现其他人整合这个补丁后出现的任何问题。

  * 合并到主线。最终,一个成功的补丁将会被合并到由Linus Torvalds管理的主线库。这时会有更多评论以及/或问题出现;重要的是开发者应负责应对这些并修复提出的问题。

  * 稳定版发布。此时补丁潜在影响的用户数量变大了,因此,新问题可能再次出现。

  * 长期维护。虽然一个开发者可能在补丁代码合并入内核之后选择忘记代码,但这种行为将在开发社区中留下一个糟糕的印象。合并代码能消除一些维护负担,因为其 他人会修复那些因API变动引发的问题。但如果补丁代码长期内依旧有用,原开发者就应该继续对此代码负责。

一些内核开发者(或他们的雇佣者)所犯的最大的错误之一就是试图将这个过程裁剪到只剩下"合并代码到主线"这一步。这种做法必将导致每个参与到其中的开发者遭遇挫折。

2.3、补丁如何进入内核

这个世上只有一个人拥有将补丁合并入内核主线库的权限,他就是Linus Torvalds。但是,在所有进入2.6.25内核的12000个补丁中,只有250个(约2%)补丁是由Linus自己挑选的。内核经过长期发展,其 规模已经大到了没有哪个单独的开发者可以在没有辅助的情况下独立逐一检查和挑选补丁的地步了。内核开发者通过使用一个助理(lieutenant)系统来 应对内核规模的增长,这个系统建立在一个信任链上。

内核代码库被逻辑上划分为一组子系统:网络、特殊架构支持、内存管理、视频设备等。大多数子系统都有一个指定的维护者,这个开发者对这个子系统内部代码整 体负责。这些子系统为护着就是他们管理的内核部分的看门人(以一种松散的方式);通常他们就是接收即将包含到主线内核的补丁的那些人。

每个子系统维护者都维护一份自己的内核源码树,通常(但不总是)使用git源码管理工具。像git(以及类似的像quilt或mercurial)这样的 工具允许维护者跟踪一个补丁列表,包括作者信息以及其他元数据。在任何特定时间,维护者都能识别出在其库中的哪个补丁在主线库中无法找到。

当合并窗口打开,最高级的维护者将会请求Linus从他们的库中"拉(pull)"出他们精调细选的补丁。如果Linus同意,补丁会向上流入他的代码 库,成为主线内核的一部分。Linus对在"拉"操作中收到的补丁的关注度不同。显然,有时,他看起来相当关切。但通常,Linus信任那些子系统维护者 不会向上合入糟糕补丁的。

同样,子系统维护者们也可以从其他维护者那里"拉"补丁。例如,网络子系统源码树构造所基于的补丁最初就是在专注于网络设备驱动、无线网络等源码树中积累 的。这种源码库链可以任意长,但很少有超过2或3个环节的。由于链中的每个维护者都相信那些管理低级别源码树的开发者,因此这个过程也被称为"信任链"。

很显然,在这样一个系统中,欲将补丁提交到内核取决于找到正确的维护者。将补丁直接发给Linus通常不是一个正确的做法。

2.4、阶段树

子系统源码树链引导补丁流合入内核,但还有一个有趣的问题:如果某人想看看为下一个合并窗口准备的所有补丁,他应该如何做?开发者们对即将到来的其他改变 十分感兴趣,这样可以知道是否有冲突需要考虑;例如,一个改变了某内核函数原型的补丁将会与其他使用了该函数旧原型的补丁发生冲突。评审者与测试者需要在 这些改变进入主线内核之前在一个整合后的内核中使用它们。开发者可以从所有感兴趣的子系统源码树上"拉"改变,不过这可是一项不轻松且很容易出错的工作。

答案是使用阶段树(staging trees),从各个子系统树中收集补丁进行测试和评审。其中最古老的一颗阶段树由Andrew Morton维护,被称为"-mm"(用于内存管理,它就是这么开始的)。-mm树(译注:现在-mm树已不再开发和维护,替代它的是-next树)集成 的补丁来自于一个长长的子系统树列表;它还集成一些旨在帮助内核调试的补丁。

除此之外,-mm树还包含了一些重要的补丁集合,这些补丁都是由Andrew直接挑选的。这些补丁可能已经被发布在一个邮件列表中了,或者他们申请成为内 核的一部分,该部分尚没有指定的子系统树。因此,-mm扮演着一种可以最后依靠的子系统树的角色;如果一个欲入内核的补丁没有明确的路径,其很可能最终成 为-mm树的一部分。其他在-mm树中积累的杂项补丁最终要么被转发到一个合适的子系统树中,要么被直接发给Linus。在一个通常的开发周期中,约有 10%的进入到主线的补丁经过-mm树。

当前的-mm补丁在下面网站的首页总是能被找到(译注:所有内核tree在http://git.kernel.org/下面可以找到):

http://kernel.org/

那些想查看当前-mm树状态的开发者可以去获取"-mm of the moment"树,在这里可以找到(译注:网页打不开):

http://userweb.kernel.org/~akpm/mmotm/

但使用MMOTM树很可能会遭遇挫折,因为它甚至可能无法编译。

另外一个阶段树linux-next是最近才创立的,该树由Stephen Rothwell维护。linux-next树是一个快照,该快照是我们在下一个合并窗口关闭后所期望看到的主线版本的样子。当补丁收集完 毕,linux-next树会在linux-kernel和linux-next邮件列表中宣布;你可以从下面地址下载(译注:下面地址无法打开):

http://www.kernel.org/pub/linux/kernel/people/sfr/linux-next/

下面地址中搜集了一些有关linux-next树的信息:

http://linux.f-seidel.de/linux-next/pmwiki/

linux-next树如何适应内核开发过程依旧在改变。截止本文撰写时,第一个包含linux-next(2.6.26)的完整开发周期接近尾声;到目 前为止,linux-next树已经被证明是一个在合并窗口开启前用于查找和修复集成问题的很有价值的资源。关于linux-next是如何运作并建立 2.6.27合并窗口的等更多信息可参考http://lwn.net/Articles/287155/。

一些开发者开始建议linux-next应该被用作未来内核开发的目标。linux-next树不倾向于超前主线版本过多,而是更多的代表那个合并了任何 新改变的树。这种想法的不足之处在于linux-next树的反复无常会使它成为一个困难的开发目标。更多有关该主题的内容请参 见:http://lwn.net/Articles/289013/,别离开;大部分涉及linux-next的内容依旧在变化。

2.5、工具

从上面内容我们可以看出,内核开发过程严重依赖于将不同方向上补丁聚集成集合的能力。如果没有合适且功能强大的工具,整个事情将无法像现在这样顺利进行下去。至于教授大家如何使用这些工具已经超出本文所涉及的内容范畴了,不过这里仍然可以给予一些提示。

目前在内核开发社区占据主导地位的源码管理系统是git。Git是自由软件社区开发的多个分布式版本控制系统中的一个。它根据内核开发的需要进行了调优, 以至于它在处理大型源码库以及大量补丁时表现优异。它还以难学难用而闻名,不过随着时间推移,它已经变得更加易用了。某种程度上的熟悉git是对内核开发 者的一个要求;即使这些开发者在日常工作中并不使用git,他们也需要git跟上其他开发者的步伐,了解其他开发者(以及主线版本)所做的工作。

Git目前几乎包含在所有Linux发行版中。其主页在:

http://git-scm.com/

这个页面上有文档和教程页面的链接。尤其是,每个开发者都应该了解"Kernel Hacker's Guide to 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来管理那些即将向上合入的补丁。对于特定种类的树(例如,-mm)的管理,quilt是最佳工具。

2.6、邮件列表

大量Linux内核开发工作通过邮件列表完成。如果一个邮件列表都没有加入,很难成为一个社区的全功能(fully-functioning)成员。不过 Linux邮件列表对于开发者来说也是潜在的风险,开发者必须冒着被大量电子邮件掩埋、与Linux邮件列表使用约定相冲突的风险,或二者兼有。

大多数内核邮件列表运行在vger.kernel.org上;主邮件列表可以在下面页面找到:

http://vger.kernel.org/vger-lists.html

但在其他地方也运行着一些邮件列表,许多列表运行在lists.redhat.com上(译注:该页面已经无法打开)。

内核开发的核心邮件列表毫无疑问是linux-kernel。这个列表是一个令人生畏的地方;每天的邮件数量可达500封,充斥着各种噪音,对话里技术性 强,并且参与者不总是那么在意礼貌。不过,这世上没有其他地方是内核开发社区会作为整体一起参与的;那些拒绝加入这个列表的开发者将错过重要信息。

这里有一些提示可以帮助你在linux-kernel这个列表中生存下去:

  * 将这个列表中的mail放到一个单独的文件夹中,而不是放在你的主收件箱中。大家必须能在一段可接受的时间段里忽略掉这个邮件流。

  * 不要尝试关注每个对话–没有人这么做。对感兴趣的话题(但注意,长期进行的对话很可能偏离了原来的主题,但电子邮件的主题行却没有改变)以及参与者进行过滤很重要。

  * 不要上钩。如果有人试图挑起一次愤怒的回应,忽略它们。

  * 当回复linux-kernel列表邮件时(或在其他邮件列表),为所有参与者保留抄送列表。如果没有足够充分的理由(例如一个显式请求),你永远不应该 删除收件人。总是确保你回应的人在抄送列表中。这个约定也使得大家不再需要显式地请求在响应你的邮件中加上你的邮件地址了。

  * 在提问之前搜索邮件列表的归档(以及整个互联网)。一些开发者对那些显然没有做作业的人很不耐烦。

  * 避免上方张贴(即将你的答案放在你所回应的引用文字的上方)。因为这将使得你的回应难于阅读并且给大家留下一个糟糕的印象。

  * 在正确的邮件列表上提问。Linux-Kernel列表可能是一个一般的交汇点,但它却不是一个可以找到所有子系统开发者的最佳地方。

最后一点–找到正确的邮件列表–是一个新手开发者共同犯错的地方。在linux-kernel上咨询网络相关问题的开发者几乎肯定可以收到一 个礼貌的建议:去netdev列表提问,因为那里才是大多数网络开发者经常提问的地方。还有其他子系统列表,诸如SCSI、video4linux、 IDE、filesystem等。查找邮件列表的最好的地方是与内核代码打包在一起的MAINTAINERS文件。

2.7、开始内核开发

有关如何开始内核开发过程的问题十分常见–既有来自个体的,也有来自公司的。而同样常见的是那些导致开发者与社区的初始关系变得更加困难的失误。

公司常常指望雇佣到知名开发者以启动一个开发组。事实上,这种方法很有效。不过这样做的成本十分昂贵,并且并没有使得有经验的内核开发者池得以扩充。如果 给予一点时间上的投资,完全可能促使内部开发者加快Linux内核开发。花费这些时间能够让一个雇主拥有一批既懂内核又了解公司的开发人员,并且他们也可 以帮助训练其他开发人员。从中期效果来看,这往往是一个更加有利可图的方法。

个体开发者经常困惑于从何开始,这是可以理解的。开始一个大项目的开发可能让人感觉胆怯;人们起初常常用更小的项目试探。正因为如此,一些开发者机遇创建 一些补丁修正一些拼写错误或一些不重要的代码风格问题。不幸地是,这样的补丁制造了一些噪音,干扰了开发社区的整体开发。因此,开发者们越来越轻视这些补 丁。那些希望将自己介绍给社区的开发者们通过这种方式将无法得到他们期望的那种对待。

Andrew Morton给那些有抱负的内核开发者以下建议:

内核开发新手的第一个项目无疑应该是"确保内核可以始终在你拥有的所有机器上完美地运行"。这事通常的做法是与其他人合作解决问题(这可能需要坚持和毅力!)。不过这就挺好了– 它就是内核开发的一部分。
 
(http://lwn.net/Articles/283982/)

在缺少问题去修正的时候,一般来说我们建议开发者看看当前的regressions列表以及处于open状态的bugs。永远不会缺少待修复的问题;解决这些问题,开发者将获得有关过程方面的经验,同时,建立起同开发社区中其他开发者间的尊重。




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:


以太币:


如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多