为什么 Linux 模块 API 不向后兼容?更新 Linux 内核后,我苦恼于找不到更新的驱动程序。
我有一个需要专有驱动程序的无线适配器,但制造商大约 7 年前已停产该设备。由于代码非常旧并且是为 Linux 2.6.0.0 编写的,因此它不能使用最新的 Linux 内核进行编译。我使用过很多 Linux 发行版,但同样的问题随处可见。尽管有随 Linux 内核一起分发的开源驱动程序,但它不起作用。有些人试图修改旧的专有代码,使其与最新的 Linux 内核兼容,但是当新的 Linux 内核发布时,需要几个月的时间才能使代码与其兼容。在此期间,又发布了另一个新版本。因此,我无法升级到新的 Linux 内核;有时我什至无法升级我的发行版。
答案1
尽管我为 Linux 内核贡献了一些(非常小的)补丁,但我并不认为自己是内核开发人员。然而,这是我所知道的:
为内核版本 2.6.0.0 编写的驱动程序早于日期消除大内核锁 (BKL)这发生在内核版本 2.6.39 中。
BKL 是在 Linux 还是单处理器(单核、单线程)操作系统时创建的。一旦添加了 SMP 支持,开发人员就认识到 BKL 在某些时候将成为一个大瓶颈,但只要系统中总共只有几个核心/线程,这在某种程度上是可以容忍的。但对于在超级计算机中使用 Linux 的人们来说,它首先成为一个真正的问题,因此工作开始用更细粒度的锁定机制或尽可能用无锁方法替换所有需要 BKL 的东西。
在现代计算机上,普通台式机和高功率笔记本电脑上的内核数量可能达到两位数,更不用说服务器了,2.6.0 向后兼容的内核模块 API 也需要实现 BKL。
如果遗留模块说“我想获取 BKL”,则内核的其余部分不知道该模块计划用它做什么,因此向后兼容机制必须获取替换 BKL 的所有锁只是为了涵盖所有的可能性。这将是一个巨大的性能打击。新的无锁方法还需要检查遗留锁——这从一开始就违背了无锁的目的。因此,即使没有实际加载遗留模块,向后兼容机制的存在也会降低系统性能。
最近,Spectre/Meltdown 安全补丁对跨越内核/用户空间边界时需要发生的情况进行了重大更改。在实施 Spectre/Meltdown 修复之前编译的任何模块对于 Spectre/Meltdown 后的内核来说都可能不可靠。
就在两周前,我正在对一台旧服务器进行故障排除,该服务器在自动化应用安全更新时需要手动重新启动电源。这种情况之前已经发生过好几次了,并且可以重现。我发现它megasr
在 Spectre/Meltdown 补丁之前有一个非常旧版本的专有存储驱动程序,该驱动程序未包含在自动更新中。将驱动程序更新到当前版本后,问题消失了。顺便说一下,这是在普通的 RHEL 6.10 系统上。
我还发现,当使用 Spectre/Meltdown 后内核加载专有的 Pre-Spectre/Meltdown 硬件监控驱动程序时,服务器会崩溃。基于这次经验,我完全相信 Spectre/Meltdown 修复需要被视为一个分水岭事件:内核和模块需要要么全部是修复前版本,要么全部是修复后版本;混合和匹配只会给值班系统管理员带来悲伤和半夜叫醒。
由于幽灵是CPU设计水平问题,它是“一份不断赠送的礼物”:有些人会找到利用弱点的新方法,然后内核开发人员需要找出阻止这些漏洞的方法。
这些只是 2.6.0.0 兼容的遗留内核模块 API 需要解决的两个大问题。我确信还有很多其他人。
还有更哲学的一面。想一想:是什么让 Linux 成为可能?
其中很大一部分是开放硬件规格。如果硬件规格开放,任何人都可以参与。由于操作系统的源代码是开放的,任何人都可以做出贡献,造福所有人。如果您的驱动程序代码是开源的,您就不能将硬件编程规范作为您的商业秘密。
Linux 内核开发人员倾向于相信开源模型。这就是为什么他们做出了设计和开发选择,以便硬件制造商参与的首选方式是开源驱动程序,将其合并到主要内核源代码发行版中,然后(和只有那时)您将受益于整个内核开发人员社区的维护。
这为硬件设计者和制造商提供了一些激励,使之成为可能。如果您有一些想要保密的东西,请努力将其封装到 ASIC 中,或者如果必须的话,也可以封装到签名的固件中。 (如果您选择后者,请授予其他人重新分发固件包的权限。)
但由于内核是开源的,内核开发者无法准确地防止其他人则无需单独维护专有驱动程序。但他们也没有动力去关心他们。
事实上,专有二进制驱动程序在内核调试中造成的额外麻烦会激励内核开发人员不是关心专有驱动程序开发:“他们让我的工作变得更加困难,为什么我应该做一些特别的事情来让他们的工作变得更容易?”
因此,内核开发人员通常会做对用户最有利的事情他们作为一个团体/社区。如果这包括一些模块 API 更改,那就这样吧。第三方驱动程序甚至不参与其中。
答案2
Greg Kroah-Hartman 在此处撰写了有关此主题的文章:https://www.kernel.org/doc/html/v4.10/process/stable-api-nonsense.html
除了一些有关编译 C 代码的技术细节之外,他还列出了一些做出决定的基本软件工程问题。
Linux 内核是总是一项正在进行的工作。发生这种情况的原因有很多:
- 新的要求随之而来。人们希望他们的软件能做更多的事情,这就是我们大多数人升级的原因,我们想要最新和最好的功能。这些可能需要对现有软件进行返工。
- 发现需要修复的错误,有时错误与设计本身有关,如果不进行重大返工就无法修复
- 软件世界中出现了新的想法和习惯,人们找到了更简单/优雅/高效的做事方式。
大多数软件都是如此, 和任何不被维护的软件都会缓慢而痛苦地死去。您要问的是为什么那些旧的、未维护的代码仍然无法工作?
为什么旧接口不维护?
为了确保向后兼容性,需要维护旧的(通常是“损坏的”和不安全的)接口。当然,理论上可以做到这一点,除非它确实具有重大意义成本。
格雷格·克罗哈特曼写道
如果 Linux 必须确保它保留稳定的源接口,那么就会创建一个新的接口,而随着时间的推移,旧的、损坏的接口将不得不维护,从而导致 [开发人员] 承担额外的工作。由于所有 Linux [开发人员] 都在自己的时间完成工作,因此要求程序员无偿、免费地做额外的工作是不可能的。
尽管Linux是开源的,但开发人员维护它的时间仍然有限。所以人力还是可以从“成本”来讨论。开发人员必须选择如何度过时间:
- 花费大量时间维护旧的/损坏的/缓慢的/不安全的接口。有时,这可能是在第一个实例中编写接口所花费的时间的两倍到三倍。
- 扔掉旧的界面并期望其他软件维护人员[做好他们的工作并且]维护自己的软件。
总而言之,分箱接口确实具有成本效益(对于内核开发人员)。如果您想知道为什么开发人员不花费数月甚至数年的时间来拯救其他人支付 10 美元对于新的 wifi 适配器...这就是原因。请记住,这对于内核开发人员来说是时间/成本效益的,但对于您或制造商来说不一定是成本效益的。