Total Pageviews

Sunday 11 March 2012

内核模块加载时的linux版本检查

2.4内核下,执行“cat /proc/ksyms”,将会看到内核符号,而且在名字后还会跟随着一串校验字符串,此校验字符串与内核版本有关。在内核源码头文件linux /modules目录下存在许多*.ver文件,这些文件起着为内核符号添加校验后缀的作用,如ksyms.ver文件里有一行#define printk _set_ver(printk),linux/modversions.h 文件会包含所有的.ver文件。所以当模块包含linux/modversions.h文件后,编译时,模块里使用的内核符号实质上成为带有校验后缀的内 核符号。在加载模块时,如果模块使用的内核符号的校验字符串与当前运行内核所导出的相应的内核符号的校验字符串不一致,即当前内核空间并不存在模块所使用 的内核符号,就会出现“Invalid module format ”的错误。
Linux内核所采用的在内核符号添加校验字符串来验证模 块的版本与内核的版本是否匹配的方法繁且会浪费内核空间,而且随着SMP、PREEMPT等机制在2.6内核的引入和完善,模块运行时对内核的依赖不再仅 仅取决于内核版本,还取决于内核的配置,此时内核符号的校验码是否一致不能成为判断模块可否被加载的充分条件。
在Linux 2.6内核的linux/vermagic.h头文件中定义了“版本魔术字符串”――VERMAGIC_STRING(如代码清单 23.7),VERMAGIC_STRING不仅包含内核版本号,还包含内核编译所使用的gcc版本、SMP与PREEMPT等配置信息。在编译模块时, 我们可以看到屏幕上会显示“MODPOST”(模块后续处理),在内核源码目录下scripts/mod/modpost.c文件中可以看到模块后续处理 部分的代码。就是在这个阶段,VERMAGIC_STRING会被添加到模块的modinfo段中,模块编译生成后,通过“modinfo mymodule.ko”命令可以查看此模块的vermagic等信息。2.6 内核下的模块装载器里保存有内核的版本信息,在装载模块时,装载器会比较所保存的内核vermagic与此模块的modinfo段里保存的 vermagic信息是否一致,两者一致时,模块才能被装载。
代码清单23.7 VERMAGIC_STRING的定义
#ifdef CONFIG_SMP   //配置了SMP

#define MODULE_VERMAGIC_SMP "SMP "

#else

#define MODULE_VERMAGIC_SMP ""

#endif

#ifdef CONFIG_PREEMPT     //配置了PREEMPT     

#define MODULE_VERMAGIC_PREEMPT "preempt "

#else

#define MODULE_VERMAGIC_PREEMPT ""

#endif 

#ifdef CONFIG_MODULE_UNLOAD  //支持module卸载

#define MODULE_VERMAGIC_MODULE_UNLOAD "mod_unload "

#else

#define MODULE_VERMAGIC_MODULE_UNLOAD ""

#endif

#ifndef MODULE_ARCH_VERMAGIC  //体系结构VERMAGIC

#define MODULE_ARCH_VERMAGIC ""

#endif

/* 拼接内核版本、上述VERMAGIC以及gcc版本 */
#define VERMAGIC_STRING                \          

 UTS_RELEASE " "                       \               

 MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT  \      

 MODULE_VERMAGIC_MODULE_UNLOAD MODULE_ARCH_VERMAGIC \                       

  "gcc-" __stringify(__GNUC__) "." __stringify(__GNUC_MINOR__)     

在通过make menuconfig对内核进行新的配置后,再基于2.6.11.5内核编译生成的新第4章中的hello.ko模块,且这个模块的modinfo结果如下:
[root@localhost driver_study]# modinfo hello.ko

filename:       hello.ko

license:        Dual BSD/GPL

author:         Song Baohua

description:    A simple Hello World Module

alias:          a simplest module

vermagic:       2.6.15.5 SMP preempt PENTIUM4 gcc-3.2

depends:       

从中可以看出,其vermagic为“2.6.15.5 SMP preempt PENTIUM4 gcc-3.2”,运行“insmod hello.ko”命令,得到如下错误:insmod: error inserting ‘hello.ko’: -1 Invalid module format
hello: version magic ‘2.6.15.5 SMP preempt PENTIUM4 gcc-3.2′ should be ‘2.6.15.5 686 gcc-3.2′
原因在于加载该hello.ko时候所使用的内核虽然还是2.6.15.5,但是和编译hello.ko时的内核的关键部分配置不一样,导致vermagic不一致发生冲突,从而加载失败。
1、我的系统是 2.4 升级到 2.6 的,编译后加载出现insmod:error inserting hello.o:-1 invalid module format…
原因参考下文:
你 的模块代码一定要为每个它要连接的内核版本重新编译 — 至少, 在缺乏 modversions 时, 这里不涉及因为它们更多的是给内核发布制作者, 而不是开发者. 模块是紧密结合到一个特殊内核版本的数据结构和函数原型上的; 模块见到的接口可能一个内核版本与另一个有很大差别. 当然, 在开发中的内核更加是这样.
内核不只是认为一个给定模块是针对一个正确的内核版本建立的. 建立过程的其中一步是对一个当前内核树中的文件(称为 vermagic.o)连接你的模块; 这个东东含有相当多的有关要为其建立模块的内核的信息, 包括目标内核版本, 编译器版本, 以及许多重要配置变量的设置. 当尝试加载一个模块, 这些信息被检查与运行内核的兼容性. 如果不匹配, 模块不会加载; 代之的是你见到如下内容:
# insmod hello.ko Error inserting ‘./hello.ko’: -1 Invalid module format
看一下系统日志文件(/var/log/message 或者任何你的系统被配置来用的)将发现导致模块无法加载特定的问题.
如果你需要编译一个模块给一个特定的内核版本, 你将需要使用这个特定版本的建立系统和源码树.
内 核接口在各个发行之间常常变化. 如果你编写一个模块想用来在多个内核版本上工作(特别地是如果它必须跨大的发行版本), 你可能只能使用宏定义和 #ifdef 来使你的代码正确建立. 本书的这个版本只关心内核的一个主要版本, 因此不会在我们的例子代码中经常见到版本检查. 但是这种需要确实有时会有. 在这样情况下, 你要利用在 linux/version.h 中发现的定义. 这个头文件, 自动包含在 linux/module.h, 定义了下面的宏定义:
UTS_RELEASE
这个宏定义扩展成字符串, 描述了这个内核树的版本. 例如, “2.6.10”.
LINUX_VERSION_CODE
这个宏定义扩展成内核版本的二进制形式, 版本号发行号的每个部分用一个字节表示. 例如, 2.6.10 的编码是 132618 ( 就是, 0×02060a ). [4]有了这个信息, 你可以(几乎是)容易地决定你在处理的内核版本.
KERNEL_VERSION(major,minor,release)
这个宏定义用来建立一个整型版本编码, 从组成一个版本号的单个数字. 例如, KERNEL_VERSION(2.6.10) 扩展成 132618. 这个宏定义非常有用, 当你需要比较当前版本和一个已知的检查点.
大 部分的基于内核版本的依赖性可以使用预处理器条件解决, 通过利用 KERNEL_VERSION 和 LINUX_VERSION_VODE. 版本依赖不应当, 但是, 用繁多的 #ifdef 条件来搞乱驱动的代码; 处理不兼容的最好的方式是把它们限制到特定的头文件. 作为一个通用的原则, 明显版本(或者平台)依赖的代码应当隐藏在一个低级的宏定义或者函数后面. 高层的代码就可以只调用这些函数, 而不必关心低层的细节. 这样书写的代码易读并且更健壮.
2、./hello:kernel –module version mismatch
hello.o was compiled for kernel version 2.4.20
while this kernel is version 2.4.20-8
原因:模块和内核版本不匹配,即编译内核的编译器与现在编译模块的编译器版本不一致。
解决方法:1> 将/usr/include/linux/version.h 文件中的#define… “2.4.20”修改成#define… “2.4.20-8”(),再重新编译!
原来主要原因是只有你真正编译过 /usr/src/linux/目录下的内核一次后,才会自动生成对应的version.h文件。以前在RedHat中拿到的Kernel-Devel包都是编译过的,所以没有这样的问题。
马上进入/usr/src/linux目录:
make menuconfig
make
搞定。。

No comments:

Post a Comment