为什么要学习内核模块
内核模块(.ko)让我们在不重编整个内核的情况下扩展内核能力。常见场景:
- 设备驱动。
- 文件系统扩展。
- 网络过滤与观测。
- 安全审计或低层性能采集。
对于运维和系统工程师,理解模块编译与加载流程能显著提升定位故障的效率。
最小可运行示例
目录结构:
simplemodule/
Makefile
simplemodule.c
Makefile:
obj-m := simplemodule.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
simplemodule.c:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("CalmOps");
MODULE_DESCRIPTION("A minimal Linux kernel module");
static int __init simple_init(void)
{
pr_info("simplemodule: loaded\n");
return 0;
}
static void __exit simple_exit(void)
{
pr_info("simplemodule: unloaded\n");
}
module_init(simple_init);
module_exit(simple_exit);
建议使用 pr_info/pr_err 而不是裸 printk,语义更清晰。
编译步骤
make
编译后常见文件:
simplemodule.ko:最终模块。Module.symvers:符号版本信息。modules.order:模块顺序。- 若干
.cmd中间文件。
加载与卸载
sudo insmod simplemodule.ko
lsmod | grep simplemodule
sudo rmmod simplemodule
lsmod | grep simplemodule
查看内核日志:
dmesg | tail -n 30
在 systemd 环境也可用:
journalctl -k -n 50
insmod vs modprobe
insmod只加载指定.ko,不自动处理依赖。modprobe会根据模块索引自动解析依赖,适合生产。
若模块已安装到标准路径,推荐:
sudo modprobe simplemodule
sudo modprobe -r simplemodule
典型报错与排障
1) invalid module format
原因通常是:
- 模块编译时内核版本与当前运行内核不一致。
- 架构不一致(x86_64/arm64)。
检查:
uname -r
modinfo simplemodule.ko
file simplemodule.ko
2) unknown symbol
说明模块引用了当前内核不可见符号。
处理:
- 确认依赖模块是否先加载。
- 检查内核配置是否启用对应功能。
- 检查导出符号策略(
EXPORT_SYMBOL)。
3) 签名相关错误(Secure Boot)
在开启 Secure Boot 的系统上,未签名模块可能无法加载。
解决方向:
- 使用发行版签名流程。
- 企业环境统一签名和证书分发。
- 实验环境临时关闭 Secure Boot(谨慎)。
开发环境准备建议
- 安装匹配当前内核的 headers/build 包。
- 使用测试机或虚拟机,不要直接在生产内核实验。
- 保留串口/控制台访问手段,避免内核崩溃后失联。
生产实践:用 DKMS 管理模块
对于长期维护的第三方驱动,推荐使用 DKMS:
- 内核升级时自动重编模块。
- 降低版本漂移导致的人工维护成本。
适用场景:网卡、存储、虚拟化驱动等。
安全与稳定性提醒
内核模块运行在内核态,任何越界、空指针、并发缺陷都可能导致系统崩溃。建议:
- 优先使用内核提供的安全 API。
- 严格做错误处理与资源回收。
- 使用静态分析和代码审查。
- 先小流量灰度,再全量上线。
小结
内核模块编译并不复杂,难点在于兼容性、排障和长期维护。掌握 make、insmod/modprobe、dmesg/journalctl 与版本匹配逻辑,就能解决大多数一线问题。
如果要进入生产环境,建议尽早引入自动化构建、签名与 DKMS 流程。
Comments