深入探究MODVERSIONS的实现原理

最近在分析Linux内核模块与内核之间的版本耦合关系时,在实验中发现了一些有趣的结果:在同一Linux版本基础上经过不同裁剪的内核,甚至是在跨度不大的两个内核版本间,内核模块可以自由互用,而且内核本身似乎具备检查这种兼容性的能力。这与之前所知的情况是不同的。在我久远的记忆中,Linux的是依靠内核模块构建时自动产生的“vermagic”标识检查是否与当前内核版本一致的,如果不一致则拒绝加载。除非在insmod/modprobe时指定参数强制忽略vermagic,但这样做的代价是如果使用错误版本的内核模块就可能导致内核崩溃。

看了一下内核这部分的源代码(所参考源码的内核版本为2.6.17.11),发现了两种不同截然的处理方式,由一个内核构建宏“CONFIG_MODVERSIONS”所控制。在它关闭的情况下,加载内核模块时,将对vermagic作全字符串的完整匹配,任何不一致均会阻止该内核模块的加载;而倘若这个宏被开启,则只有vermagic第一个空格之后的部分会参与匹配,也就是说形如“2.6.5-7.191-bigsmp”这一段内核版本标识其实并不要求一致。事实上,当上述宏开启后,除了对vermagic后半段的匹配性检查外,内核还会进行额外的“内核接口指纹”检验,才最终决定是否允许加载该内核模块。

何谓“内核接口指纹”?其实这是Linux内核在试图解决跨版本内核模块兼容性问题上的一个积极尝试。通过在内核构建流程中插入一个特殊的“内核接口指纹”提取阶段,由一个专用的GNU工具“genksyms”为内核的源代码生成“指纹”信息。这里的“指纹”其实是一组特殊的CRC,它们分别对应着内核符号表中的每一个符号。生成CRC的输入包括这个符号的完整定义,以及其中所有参数涉及的数据类型定义,包括typedef、enum、struct、union。其中typedef将展开至最原始的标准数据类型;后三种类型将包含其定义的完整组成部分(例如结构体的成员、枚举的清单),对于组成成员也将递归应用上述原则。也就是说,“指纹”信息代表了对内核接口有兼容性影响的绝大部分要素,一旦其发生变化,则意味着接口的兼容性可能已经被破坏。

除了内核本身的构建在CONFIG_MODVERSIONS打开后会包含上述指纹信息外,内核模块的构建是否包含指纹信息还依赖于构建该内核模块的内核构建环境中此宏的状态。只有当两者均包含上述指纹信息时,上述检验机制才能真正生效。想要知道当前内核是否包含了内核接口指纹,只需提取/proc/config.gz并查看其中CONFIG_MODVERSIONS的取值;而若想知晓一个内核模块是否包含指纹信息,则需要用到modprobe的下面这个参数:

modprobe --dump-modversions <module>

其实,就如Linux在内核编译菜单中对CONFIG_MODVERSIONS解释,也用了一个“hopefully”。这个机制本身的出发点是很好的,而且也尽了最大努力去校验。但事实上,接口的兼容性很难仅仅用定义来保证,因为接口所体现的功能部分是无法在定义中反应出来的,这也是程序尚无法完全取代人类的判断能力。因此,MODVERSIONS可以作为版本不配套时的后备选择(尤其对非开源的驱动程序),但不能依赖它作为兼容性的判定依据。

参考文献:Kernel Symbols and CONFIG_MODVERSIONS

Written on August 12, 2008