Linux 内核驱动接口

(你所有问题的答案,甚至更多)

Greg Kroah-Hartman <greg@kroah.com>

本文旨在解释为什么 Linux **既没有二进制内核接口,也没有稳定的内核接口**。

注意

请注意,本文描述的是**内核内部**接口,而不是内核到用户空间的接口。

内核到用户空间的接口是应用程序使用的,即系统调用接口。该接口随着时间的推移**非常**稳定,不会中断。我有一些在 0.9 之前的内核上构建的旧程序,它们在最新的 2.6 内核版本上仍然可以正常工作。该接口是用户和应用程序程序员可以信赖其稳定性的接口。

概要

你认为你需要一个稳定的内核接口,但你其实并不需要,甚至你都不知道。你真正想要的是一个稳定运行的驱动程序,而只有当你的驱动程序在主内核树中时,你才能获得它。如果你的驱动程序在主内核树中,你还会获得许多其他好处,所有这些都使 Linux 成为如此强大、稳定和成熟的操作系统,这也是你最初使用它的原因。

引言

只有那些想编写内核驱动程序的少数人需要担心内核内部接口的更改。对于世界上大多数人来说,他们既看不到这个接口,也不关心它。

首先,我不会讨论关于闭源、隐藏源代码、二进制 blob、源代码包装器或任何其他描述未在 GPL 下发布其源代码的内核驱动程序的法律问题。如果您有任何法律问题,请咨询律师,我是一名程序员,因此我将只描述这里的技术问题(不是轻视法律问题,它们是真实存在的,并且您需要始终注意它们)。

所以,这里有两个主要主题,二进制内核接口和稳定的内核源代码接口。它们相互依赖,但我们将首先讨论二进制内容,以便将其排除在外。

二进制内核接口

假设我们为内核提供了一个稳定的内核源代码接口,那么自然也会出现一个二进制接口,对吗?错误。请考虑以下关于 Linux 内核的事实

  • 根据您使用的 C 编译器的版本,不同的内核数据结构将包含不同的结构对齐,并且可能以不同的方式包含不同的函数(将函数内联与否)。单个函数的组织结构并不重要,但不同的数据结构填充非常重要。

  • 根据您选择的内核构建选项,内核可以假设各种不同的事情

    • 不同的结构可能包含不同的字段

    • 某些函数可能根本没有实现(例如,某些锁在非 SMP 构建中编译为空)。

    • 根据构建选项,内核中的内存可以以不同的方式对齐。

  • Linux 在各种不同的处理器架构上运行。来自一种架构的二进制驱动程序不可能在另一种架构上正确运行。

现在,只需使用构建内核的确切 C 编译器,为精确的特定内核配置编译模块,就可以解决许多这些问题。如果您想为特定 Linux 发行版的特定版本提供模块,这就足够了。但是,将单个构建乘以不同的 Linux 发行版的数量以及 Linux 发行版支持的不同版本的数量,您很快就会遇到不同版本上的不同构建选项的噩梦。还要意识到,每个 Linux 发行版都包含许多不同的内核,所有这些内核都针对不同的硬件类型(不同的处理器类型和不同的选项)进行了调整,因此即使对于单个版本,您也需要创建多个版本的模块。

相信我,如果你试图支持这种发布,你最终会发疯,我很久以前就吸取了这个惨痛的教训...

稳定的内核源代码接口

如果你与那些试图保持不在主内核树中的 Linux 内核驱动程序随着时间的推移保持最新的人交谈,这是一个更加“不稳定”的话题。

Linux 内核开发是持续的并且以快速的速度进行,永远不会停止减速。因此,内核开发人员会在当前接口中发现错误,或者找到更好的方法来做事。如果他们这样做,他们就会修复当前的接口以使其更好地工作。当他们这样做时,函数名称可能会更改,结构可能会增长或缩小,并且函数参数可能会被修改。如果发生这种情况,内核中所有使用此接口的实例都会同时修复,以确保一切继续正常工作。

作为这方面的一个具体例子,内核内部的 USB 接口在其子系统的生命周期中至少经历了三次不同的重新设计。这些重新设计是为了解决许多不同的问题

  • 从同步数据流模型更改为异步数据流模型。这降低了许多驱动程序的复杂性,并提高了所有 USB 驱动程序的吞吐量,因此我们现在几乎所有 USB 设备都以其最大可能速度运行。

  • 更改了 USB 驱动程序从 USB 核心分配数据包的方式,以便所有驱动程序现在都需要向 USB 核心提供更多信息,以修复许多已记录的死锁。

这与许多闭源操作系统形成鲜明对比,后者不得不随着时间的推移维护其旧的 USB 接口。这使得新开发人员可能会意外地使用旧接口并以不正确的方式做事,从而导致操作系统的稳定性受到影响。

在这两种情况下,所有开发人员都同意这些是需要做出的重要更改,并且它们已经完成,且痛苦相对较少。如果 Linux 必须确保它将保留一个稳定的源代码接口,则会创建一个新接口,并且必须随着时间的推移维护旧的、损坏的接口,从而给 USB 开发人员带来额外的工作。由于所有 Linux USB 开发人员都在自己的时间工作,要求程序员免费做额外的工作是行不通的。

安全问题对于 Linux 也非常重要。当发现安全问题时,会在很短的时间内修复它。很多时候,这导致内部内核接口被修改,以防止安全问题发生。当这种情况发生时,所有使用这些接口的驱动程序也会同时修复,以确保安全问题得到修复,并且不会在未来的某个时间意外地再次出现。如果不允许更改内部接口,则无法修复此类安全问题并确保其不会再次发生。

内核接口会随着时间的推移进行清理。如果当前没有人在使用某个接口,则会将其删除。这确保了内核尽可能保持小巧,并且所有潜在的接口都经过尽可能好的测试(未使用的接口几乎不可能测试其有效性)。

该怎么办

因此,如果您有一个不在主内核树中的 Linux 内核驱动程序,那么作为开发人员,您应该怎么做?为每个发行版的每个不同内核版本发布二进制驱动程序是一场噩梦,并且试图跟上不断变化的内核接口也是一项艰巨的工作。

很简单,将您的内核驱动程序放入主内核树(请记住,我们在这里讨论的是在 GPL 兼容许可下发布的驱动程序,如果您的代码不属于此类别,祝你好运,您只能靠自己了,你这个吸血鬼)。如果您的驱动程序在树中,并且内核接口发生了更改,那么它将由首先进行内核更改的人修复。这确保了您的驱动程序始终可构建,并且随着时间的推移正常工作,而您只需付出很少的努力。

将驱动程序放入主内核树的非常好处是

  • 随着维护成本(对原始开发人员而言)的降低,驱动程序的质量将会提高。

  • 其他开发人员将为您的驱动程序添加功能。

  • 其他人将在您的驱动程序中查找并修复错误。

  • 其他人将在您的驱动程序中找到调整机会。

  • 当外部接口更改需要时,其他人将为您更新驱动程序。

  • 该驱动程序会自动在所有 Linux 发行版中发布,而无需请求发行版添加它。

由于 Linux 开箱即用支持的设备数量比任何其他操作系统都多,并且它在比任何其他操作系统都更多的处理器架构上支持这些设备,这种久经考验的开发模式肯定有其独到之处 :)


感谢 Randy Dunlap、Andrew Morton、David Brownell、Hanna Linder、Robert Love 和 Nishanth Aravamudan 对本文早期草稿的审阅和评论。