如何编写一个真正能构建的Linux内核模块

如何编写一个真正能构建的Linux内核模块

💡 原文英文,约3200词,阅读约需12分钟。
📝

内容提要

本文以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。

🏷️

标签

➡️

继续阅读