最常见报错是缺内核头文件,ubuntu/debian装linux-headers-$(uname -r),rhel/centos/fedora装kernel-devel;invalid module format因编译与运行内核配置或版本不一致;模块崩溃多因内存越界、未初始化指针或__init函数误用;卸载残留需在module_exit()中严格逆序清理资源。

内核模块编译失败:make: *** /lib/modules/$(uname -r)/build: No such file or directory
这是最常见报错,本质是缺内核头文件,不是缺编译器。系统里只有运行时内核(vmlinuz),没装对应开发包。
- Ubuntu/Debian 系统直接装
linux-headers-$(uname -r),别漏掉$(uname -r)要展开成实际版本,比如6.8.0-45-generic - RHEL/CentOS/Fedora 用
kernel-devel,且必须和uname -r输出完全一致;如果升级过内核但没重启,uname -r和当前运行内核不匹配,也会报这个错 -
/lib/modules/$(uname -r)/build是软链接,指向/usr/src/linux-headers-xxx,检查它是否存在、是否可读、是否指向真实目录
加载模块时报 Invalid module format
模块编译时用的内核配置或版本和当前运行内核不一致,尤其是 CONFIG_MODULE_SIG、CONFIG_DEBUG_INFO 这类开关不同,会导致二进制签名或符号表不兼容。
- 确认
make时用的是/lib/modules/$(uname -r)/build,不是随便找的源码树 - 不要跨大版本编译,比如用 6.5 的头文件编译模块,再加载到 6.8 内核上——即使能加载,也可能在调用
struct成员时崩溃 - 若启用了模块签名(
CONFIG_MODULE_SIG=y),必须用scripts/sign-file签名,否则内核拒绝加载,错误还是Invalid module format
insmod 成功但 lsmod 看不到,或立即触发 kernel oops
模块代码本身有内存越界、未初始化指针、或用了被标记为 __init 的函数,加载后就释放了,后续调用就崩。
- 检查
module_init()函数里有没有调用printk()以外的高危操作:比如分配大内存用kmalloc(GFP_KERNEL)在原子上下文里、访问用户空间地址没用copy_from_user() - 确保所有导出符号(
EXPORT_SYMBOL_GPL())确实存在且可见,特别是依赖其他模块时,对方模块要先加载,且符号未被static修饰 - 用
dmesg -T立即看内核日志,oops信息里会带寄存器状态和栈回溯,关键线索在Call Trace:下面那一行,通常指向你模块的某个函数名
模块卸载后残留资源导致再次加载失败
rmmod 只是调用 module_exit() 并释放模块内存,但如果你忘了释放设备号、注销字符设备、释放 request_irq() 占用的中断,下次加载就会卡在 register_chrdev_region() 或 request_irq() 返回 -EBUSY。
-
module_exit()必须和module_init()严格逆序清理:先unregister_chrdev_region(),再unregister_chrdev();先free_irq(),再iounmap() - 用
cat /proc/devices查看主设备号是否还在,用cat /proc/interrupts确认中断线是否仍关联你的模块名 - 加个
static int __init my_init(void) { if (my_dev) return -EBUSY; ... },避免重复加载,比靠人记住更可靠
事情说清了就结束










