7. 高级主题¶
到目前为止,希望您已经掌握了开发流程是如何运作的。不过,还有更多内容需要学习!本节将涵盖一系列主题,对于想要成为 Linux 内核开发流程中常规一员的开发者来说,这些主题会非常有用。
7.1. 使用 git 管理补丁¶
内核对分布式版本控制的使用始于 2002 年初,当时 Linus 开始接触专有的 BitKeeper 应用程序。虽然 BitKeeper 饱受争议,但它所体现的软件版本管理方法绝对没有争议。分布式版本控制使内核开发项目得到了立竿见影的加速。目前,有几种 BitKeeper 的免费替代品。不管好坏,内核项目最终选择 git 作为其首选工具。
使用 git 管理补丁可以大大简化开发人员的工作,尤其是随着补丁数量的增长。Git 也有其粗糙之处并带来某些风险;它是一个年轻且强大的工具,其开发人员仍在对其进行完善。本文档不打算教读者如何使用 git;单凭这一点就足以写成一篇很长的文档。相反,这里的重点将放在 git 如何具体融入内核开发流程中。希望快速掌握 git 的开发者可以在以下网址找到更多信息:
以及网络上找到的各种教程。
首先要做的是阅读上述网站,在尝试使用 git 向他人提供补丁之前,对 git 的工作原理有扎实的理解。使用 git 的开发者应该能够获取主线仓库的副本、探索修订历史、将更改提交到树中、使用分支等。理解 git 用于重写历史的工具(例如 rebase)也很有用。Git 有其自己的术语和概念;git 的新用户应该了解引用(refs)、远程分支、索引(index)、快进合并(fast-forward merges)、推送和拉取(pushes and pulls)、分离头指针(detached heads)等。刚开始时这一切可能会有点让人望而生畏,但经过一番学习,这些概念并不难掌握。
在逐步熟练的过程中,使用 git 生成通过电子邮件提交的补丁是一个很好的练习。
当你准备好开始搭建供其他人查看的 git 树时,你当然需要一个可以被拉取(pull)的服务器。如果你有一个可以连接到互联网的系统,使用 git-daemon 设置这样一个服务器相对简单。否则,免费的公共托管网站(例如 GitHub)开始在网上出现。成熟的开发者可以在 kernel.org 上获得一个账户,但这些账户并不容易获得;有关更多信息,请参见 https://linuxkernel.org.cn/faq/。
正常的 git 工作流涉及大量分支的使用。每一条开发线都可以分离到一个单独的“主题分支(topic branch)”中并进行独立维护。Git 中的分支成本很低,没有理由不随意使用它们。而且,无论如何,你不应该在你打算让别人拉取的任何分支中进行开发。公开可用的分支应当谨慎创建;当开发分支中的补丁处于完整状态并准备就绪时——而不是在这之前——才能将其合并进来。
Git 提供了一些强大的工具,允许你重写开发历史。一个带来不便的补丁(比如破坏二分查找的补丁,或者具有某种其他明显漏洞的补丁)可以在原地修复,或者从历史中完全抹去。一组补丁可以被重写,就像它是基于今天的主线编写的一样,尽管你已经为此工作了几个月。更改可以透明地从一个分支转移到另一个分支。诸如此类。明智地使用 git 修改历史的能力有助于创建问题更少、更干净的补丁集。
不过,过度使用这一功能除了会导致对创建完美项目历史的单纯执念外,还会引发其他问题。重写历史会重写该历史中包含的更改,将一个经过测试的(希望如此)内核树变成一个未测试的内核树。不仅如此,如果开发者对项目历史没有共同的看法,他们就很难进行协作;如果你重写了其他开发者已经拉取到他们仓库中的历史,你将给这些开发者带来极大的不便。因此,这里有一个简单的经验法则:一旦向他人导出了历史,通常就应将其视为不可变的。
因此,一旦你将一组更改推送到公开可用的服务器上,这些更改就不应该再被重写。如果你尝试推送不会导致快进合并(fast-forward merge)的更改(即不共享相同历史的更改),git 将尝试强制执行此规则。可以覆盖此检查,并且有时可能确实需要修改已导出的树。为了避免在 linux-next 中发生冲突而在树之间移动变更集就是这样一个例子。但此类操作应该很少见。这也是为什么开发应该在私有分支中进行(必要时可以重写),并且只有当其处于相当成熟的状态时才移动到公共分支中的原因之一。
随着主线(或一组更改所基于的其他树)的推进,人们很容易产生与该树进行合并以保持领先地位的想法。对于私有分支而言,变基(rebasing)可能是跟上另一个树的简单方法,但一旦树被导出给全世界,变基就绝不可行了。一旦发生这种情况,必须进行完全合并。偶尔合并很有意义,但过于频繁的合并会无谓地弄乱历史。这种情况下建议的技术是:不经常合并,并且通常只在特定的发布点(例如主线 -rc 版本)进行合并。如果你对某些特定的更改感到不安,你始终可以在私有分支中执行测试合并。Git 的 “rerere” 工具在这种情况下非常有用;它会记住合并冲突是如何解决的,这样你就不比做两次同样的工作。
关于像 git 这样的工具,最常反复出现的抱怨之一是:大量补丁从一个仓库转移到另一个仓库,使得一些未经深思熟虑的更改很容易在审查雷达下方溜进主线。当看到这种事情发生时,内核开发者往往会感到不高兴;发布包含未审查或跑题补丁的 git tree 可能会影响你以后让别人拉取你的代码树的能力。引用 Linus 的话:
You can send me patches, but for me to pull a git patch from you, I
need to know that you know what you're doing, and I need to be able
to trust things *without* then having to go and check every
individual change by hand.
(https://lwn.net/Articles/224135/)。
为了避免这种情况,请确保给定分支内的所有补丁都紧密贴合相关主题;“驱动修复(driver fixes)”分支不应修改核心内存管理代码。而且,最重要的是,不要使用 git tree 来绕过审查流程。偶尔向相关邮件列表发布该树的摘要,并在适当时机请求将该树纳入 linux-next 中。
如果(当)其他人开始发送补丁请求加入你的树中时,不要忘记审查它们。还要确保维护正确的作者信息;git “am” 工具在这方面已经尽力了,但如果补丁是通过第三方转发给你的,你可能需要向补丁添加一个 “From:” 行。
在请求 pull 时,务必提供所有相关信息:你的树在哪里、要拉取哪个分支、以及拉取将产生哪些更改。git request-pull 命令在这方面会很有帮助;它会按照其他开发者的期望格式化请求,还会检查以确保你没有忘记将这些更改推送到公共服务器。
7.2. 审查补丁¶
某些读者肯定会反对将本节归入“高级主题”,理由是连初学内核的开发者也应该在审查补丁。诚然,要学习如何在内核环境中编程,没有什么比查看他人发布的代码更好的方法了。此外,审阅者永远供不应求;通过查看代码,你可以为整个流程做出重大的贡献。
审查代码可能是一个令人望而生畏的前景,尤其是对于新内核开发者而言,他们可能会对公开质疑经验更丰富的开发者所发布的代码感到紧张。不过,即使是最有经验的开发者编写的代码也可以改进。对审阅者(所有审阅者)最好的建议或许是:将审阅意见表述为问题,而不是批评。问一句“在这个路径中锁是如何释放的?”总是比断言“这里的加锁是错的”效果更好。
在出现分歧时,另一个有用的技巧是请求其他人插话。如果讨论在几次交流后陷入僵局,不妨呼吁其他审阅者或维护者发表意见。通常,那些同意审阅者观点的人在被点名之前会保持沉默。多人的意见具有成倍的权重。
不同的开发者会从不同的角度审查代码。有些人主要关心的是代码风格以及代码行尾是否有空白字符。其他人则主要关注补丁作为一个整体实现的更改对内核来说是否是一件好事。还有人会检查是否有有问题的加锁、过度的栈使用、可能的安全问题、在其他地方已存在的代码重复、充分的文档、对性能的不利影响、用户空间 ABI 更改等。所有类型的审查,只要能带来更好的代码进入内核,都是受欢迎且有价值的。
没有严格要求使用诸如 Reviewed-by 之类的特定标签。事实上,用纯英语撰写的审查意见信息更丰富,即使提供了标签也是受到鼓励的,例如:“我查看了此提交的 A、B 和 C 方面,我认为它很好。”某种形式的审查信息或回复显然是必不可少的,否则维护者将根本不知道审阅者是否看过该补丁!
最后但并非最不重要的一点是,补丁审查可能会变成一个消极的过程,专注于指出问题。请偶尔给予一些赞扬,尤其是对新手!