内容提要
本文以22行的最小Linux内核模块为例,介绍其构建与加载机制。模块编译后约106KB,去除调试信息后仅4.8KB;构建经编译、MODPOST符号校验、链接等步骤生成.ko文件。模块依赖内核导出符号,vermagic校验决定能否加载,不匹配需重建。文章还涉及模块参数传递、常见构建错误、旧教程失效原因,并强调模块与内核同权限,需签名与锁定保护。
延伸解读
模块体积的真相:调试信息占大头
编译后的.ko文件约106KB,但去除调试信息后仅4.8KB,超过95%的体积是DWARF调试数据。这些数据用于crash、gdb等工具分析内核崩溃,加载时并不会载入内存。因此模块实际内存占用很小,但磁盘上的大小受编译路径、编译器版本和内核配置影响,具体字节数因环境而异,比例关系则相对稳定。
vermagic校验:模块与内核的硬性匹配
内核加载模块前会比对vermagic字符串,涵盖内核版本、SMP支持、模块卸载和符号版本化等。不匹配则拒绝加载,因为内核内部结构无ABI稳定性,强行加载可能导致内存损坏。这解释了为何跨机器或升级内核后需重新编译模块,也是DKMS存在的根本原因——它自动为每个新内核重建第三方模块。
构建错误排查:从报错定位根因
常见构建错误各有明确指向:缺少内核头文件会报“No rule to make target 'modules'”;MODPOST阶段出现“undefined”表示引用了未导出符号,重试无效;加载时“Invalid module format”是vermagic不匹配,需重新编译;“Operation not permitted”通常源于Secure Boot或lockdown阻止未签名模块。理解这些错误可快速缩小问题范围。
模块权限与安全:内核级的信任边界
加载的模块与内核拥有同等权限,可读写任意内存、修改函数、绕过策略。因此模块加载无法仅靠权限模型约束,内核转而依赖签名和lockdown机制。加载任何树外模块都会给内核打上taint标记,记录在/proc/sys/kernel/tainted中,内核开发者通常要求在不tainted的内核上复现bug。
Q&A
一个最小的Linux内核模块编译后为什么有106KB,而strip后只有4.8KB?
编译产生的.ko文件约106KB,其中约95%是DWARF调试信息,用于crash、gdb等工具分析。strip --strip-debug移除调试段后,实际模块代码仅4.8KB。加载时内核不加载调试段,所以内存占用是小的那个数。
为什么我的内核模块加载时提示“Invalid module format”?
这通常是因为vermagic字符串与当前运行内核不匹配。vermagic包含内核版本、SMP、模块卸载、符号版本等信息。用modinfo ./hello.ko | grep vermagic对比uname -r,不匹配则需针对正确内核头文件重新编译。
编写内核模块时,MODPOST步骤是做什么的?
MODPOST扫描目标文件,检查引用的未定义符号是否在内核导出符号表中。如果使用了内核未导出的符号,构建会失败并报“undefined symbol”错误。它还生成包含模块元数据的hello.mod.c文件。
如何给内核模块传递参数?
使用module_param宏声明变量、类型和权限,例如module_param(who, charp, 0444)。加载时通过insmod ./param.ko who=kernel times=3传递。MODULE_PARM_DESC可添加描述,modinfo可查看。
为什么旧的内核模块教程现在编译不过?
常见原因包括:使用init_module/cleanup_module旧约定而非module_init/module_exit;Makefile中使用已移除的SUBDIRS=而非M=;头文件路径过时如/usr/src/linux;以及MODULE_LICENSE现在必须声明以访问GPL符号。
加载内核模块时有哪些安全限制?
Secure Boot会拒绝未签名的模块,内核锁定(lockdown)在机密性模式下会阻止加载。模块与内核同权限,可读写任意内存、修改函数,因此需要签名和锁定保护。检查mokutil --sb-state和/sys/kernel/security/lockdown。