linux内核对插入的内核模块进行严格的版本检查,即使一个小版本号不一致也会导致加载的不成功,这完全是为了内核本身运行安全。由于linux内核的发布是基于版本号的,而所有的内核模块的开发必须依赖内核头文件--其使用的内核导出符号均在头文件中,该头文件肯定属于一个特定版本的源码树,因此模块也就间接依赖了该版本的源码树。那么到底为何内核对模块的版本检查如此严格呢?因为每当一个新的版本发布,可能导致接口的改变,如果不严格检查版本则可能导致内核crash,然而如果旧版本的模块难道没有任何办法载入到新版本的内核中吗?不是的!只要它使用的接口在两个版本间没有改变即可,这是通过一个非常有意思的机制实现的,这就是单独接口符号的crc校验机制。其实windows的ddk编译的驱动要想载入内核而不发生意外,接口的一致性也是必要的,只是ddk的编译环境为开发者屏蔽了很多版本方面的信息,第一,ddk自带了头文件并且windows的内核无法单独进行下载,它是和操作系统一起被发行的;第二,操作系统的内核,库,ddk是由微软统一管理的。因此,我们会觉得ddk编译出的驱动对系统版本没有要求,其实这是不对的,在ddk的Build Environments中,不是也有版本之分别吗?只是它的版本没有linux那么多罢了!
     首先要知道,内核中只要是EXPORT的符号,都会有一个crc校验码,这些校验码保存在内核映像中,这些校验码用于和载入内核的模块中包含的crc校验码作比较。模块编译的过程中,genksyms这个程序起了很大的作用,其实编译模块最终的结果--一个ko文件并不仅仅是你的模块源代码c文件经过编译器和链接器处理过后的结果,而是被genksyms插入了一些额外的数据,就是这些数据作为版本控制的依据起了很大的作用。简单来说就是genksyms分析模块源代码文件(编译预处理后的),将其中使用的函数,变量等抽出来,然后再为这些函数,变量中的每一个生成一个crc校验码,生成校验码输入就是函数的名称,参数类型以及变量的名称,类型等,然后这些crc校验和被写入一个.mod.c文件中,最终编译器和链接器将这个.mod.c文件和源代码文件一起编译和链接,最终生成的ko中就包含了这些由genksyms生成的crc校验和。
     当模块加载的时候,内核会逐一的抽出ko模块中的每一个符号以及其对应的crc校验码,然后和内核本身保存的该符号的crc校验码做比较,只要有一个符号的crc校验码不同就说明该模块的版本不正确,因为如果该模块确实是在当前内核版本下编译的,那么其符号的crc校验码将会和内核本身相应符号的crc校验码一致,毕竟它们的算法一样,输入信息也一样。
     以上的这种机制可以被用作一种动态的版本控制,比如动态链接库的版本控制,这样可以减轻库混乱带来的危害,我们需要作的仅仅是为每一个动态库生成一个类似.mod.c的文件,姑且称为.version.c,这个文件中包含所有引用接口的crc校验码以及引用接口本身,然后修改动态库的加载机制,增加版本检查相关的逻辑,也就是一个crc比对的逻辑。