Linux内核驱动程序接口 【ChatGPT】

发布时间 2023-12-08 22:30:27作者: 摩斯电码

Linux内核驱动程序接口

(回答你的所有问题以及更多)

Greg Kroah-Hartman greg@kroah.com

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

注意

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

内核与用户空间的接口是应用程序使用的系统调用接口。该接口随着时间的推移非常稳定,不会发生变化。我有一些早期构建在0.9之前内核上的程序,在最新的2.6内核版本上仍然可以正常运行。这个接口是用户和应用程序员可以依赖的稳定接口。

执行摘要

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

介绍

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

首先,我不打算讨论任何与闭源、隐藏源代码、二进制代码块、源代码包装器或其他不在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对本文初稿的审查和意见。