设备驱动
文件系统
系统调用
Linux 的迅速发展致使相邻版本的内核之间亦存在较大的差异,即在版本补丁号(Patch Level,即内核版本号的第四位数)相邻的内核之间。为此 Linux 的开发者为了保证内核的稳定,Linux 在加载模块到内核时对模块采用了版本校验机制。
1,Invalid module format -> disagrees about version of symbol module_layout
由于模块中 module_layout 的导出符号的版本信息与当前内核中的不符
函数 module_layout 的第二个参数 ver 存储了模块的版本校验信息。结构体 modversion_info 中保存了用于模块校验的 CRC(Cyclic Redundancy Check,即循环冗余码校验)值
2,Linux 对可装载模块采取了两层验证:模块的 CRC 值校验和 vermagic 的检查
CRC 值校验针对模块(内核)导出符号,是一种简单的 ABI(即 Application Binary Interface)一致性检查(1中的disagrees about version of symbol module_layout 就有由于没有通过 CRC 值校验,即 module_layout 的 CRC 值与当前内核中的不符)
模块 vermagic(即 Version Magic String)则保存了模块编译时的内核版本以及 SMP 等配置信息
3,模块的 vermagic 信息
# uname – r
2.6.38-10-generic
# modinfo ./hello/hello.ko
filename: ./hello/hello.ko
license: Dual BSD/GPL
srcversion: 31FE72DA6A560C890FF9B3F
depends:
vermagic: 2.6.38-9-generic SMP mod_unload modversions
4,modversion保存位置
通常,内核与模块的导出符号的 CRC 值被保存在文件 Module.symvers 中,该文件需在开启内核配置选项 CONFIG_MODVERSIONS 之后并完全编译内核获得
或者也可在编译外部模块后获得该文件,保存的是模块的导出符号的 CRC 信息
5,校验
Linux 内核在进行模块装载时先完成模块的 CRC 值校验,再核对 vermagic 中的字符信息
从用户空间装载模块到内核时,Linux 还对用户权限进行了检查。模块的装载须是获得 CAP_SYS_MODULE 权限的超级用户,这正是模块装载时最先检查的内容
6,module版本信息保存在ko文件的哪里?
Linux 使用 GCC 中的声明函数属性 attribute 完成对模块的版本信息附加。构建的模块存在几个 section,如 .modinfo、.gnu.linkonce.this_module 和 __versions 等,这些 ELF 小节(即 section)保存了模块校验所需的信息(关于这些 section 信息的附加过程,您可查看模块构建时生成的文件 .mod.c 及工具 modpost
模块 CRC 值校验查看的是就是模块 __versions 小节的内容,即是附加的 struct modversion_info 信息
模块的 CRC 校验过程在函数 setup_load_info 中完成
Linux 使用 .gnu.linkonce.this_module 小节来解决模块对 struct module 信息的附加
模块 vermagic 信息则被保存在了 ELF 的 .modinfo 小节中
7,vermagic与内核版本号
vermagic 信息来自内核头文件 include/linux/vermagic.h 中的宏 VERMAGIC_STRING,其中宏 UTS_RELEASE 保存了内核版本信息。与其关联的头文件 include/generated/utsrelease.h 需经内核预编译生成,即通过命令 make 或 make modules_prepare 等
/* kernel/module.c */
static const char vermagic[] = VERMAGIC_STRING;
/* include/linux/vermagic.h */
#define VERMAGIC_STRING \
UTS_RELEASE ” ” \
MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT \
MODULE_VERMAGIC_MODULE_UNLOAD MODULE_VERMAGIC_MODVERSIONS \
MODULE_ARCH_VERMAGICLINE
在 Linux 2.6 中,工具 insmod 被重新设计并作为工具集 module-init-tools 中的一个程序,其通过系统调用 sys_init_module(您可查看头文件 include/asm-generic/unistd.h)衔接了模块的版本检查,模块的装载等功能
1,.mod.c
文件 .o 是模块代码(即 .c 文件)经编译后获得的目标文件,文件 .mod.o 则对应文件 .mod.c。文件 [module].mod.c 是对 [modulue].c 的扩展,清单展示了文件 kobject-example.mod.c 的内容 ( 即模块 kobject-example.ko 的 .mod.c 文件 ),可见到与模块版本检查相关三个小节:
# cat ./kobject/kobject-example.mod.c
…
MODULE_INFO(vermagic, VERMAGIC_STRING);
struct module __this_module
attribute((section(“.gnu.linkonce.this_module”))) = {
…
};
static const struct modversion_info ____versions[]
__used
attribute((section(“__versions”))) = {
…
};
static const char __module_depends[]
__used
attribute((section(“.modinfo”))) =
“depends=”;
MODULE_INFO(srcversion, “B06F9B8B7AB52AEED247B9F”);
2,ko来自.o与.mod.o
.ko 的附加信息合并自文件 .o 与文件 .mod.o。内核工具 modpost 完成了一这步骤
3,内核版本号在哪里?
经上述,我们可知内核树的顶层 Makefile 文件包含了内核版本的信息,且该信息经编译后被添加到模块的(头文件 include/generated/utsrelease.h 保存的内核版本信息来自顶层 Makefile)
4,/proc/modules
lsmod 打开文件 /proc/modules 查询当前内核中已装载的模块,文件 /proc/modules 还被 rmmod 在卸载模块时使用
5,模块CRC信息生成
Linux 2.6 构建模块时工具 modpost 被 scripts/Makefile.modpost 调用,生成 [module].mod.c 及文件 Module.symvers。在开启内核选项 CONFIG_MODVERSIONS 之后,文件 Makefile.Build 会调用工具 genksyms(现位于内核树 scripts/genksyms 目录下,在 Linux 2.4 时是模块工具集 Modutils 的一部分)生成 CRC 信息
1,预编译内核模块
# make menuconfig
# make modules_prepare
2,使内核支持模块的版本检查
CONFIG_MODULES=y
CONFIG_MODVERSIONS=y
另须注意的是,模块预编译并不生成 Module.symvers 文件,即使开启了 CONFIG_MODVERSIONS 选项。因此最好的方式是完全编译 Linux 内核