insmod 实操指南——从写一个内核模块到加载运行
insmod 实操指南——从写一个内核模块到加载运行
本文讲清
insmod:它干什么、内核模块(.ko)是什么、insmod与modprobe的区别、模块签名这个最常见的坑,并给一个可直接照抄跑通的 hello world 模块从编写→编译→加载→验证→卸载全流程。前置:ring0/ring3、内核态概念见本人《x86 体系结构基础》篇。内核模块就是跑在 ring0、和内核同地址空间的代码。
一、insmod 是什么、内核模块又是什么
**内核模块(Loadable Kernel Module, LKM)**是一段可动态加载到内核地址空间、跑在 ring0 的代码,扩展内核功能而不需重启、不需重编内核。文件后缀 .ko(kernel object)。典型用途:设备驱动、文件系统、网络协议、安全 hook(如 alcor-chaos 的 libnvml-hijack/libixml-hijack 就是劫持库的内核态配合件)。
insmod(insert module)是用户态命令,把一个 .ko 文件加载进内核。它本质是调一次 init_module(2) / fini_module(2) 系统调用(或 finit_module 带 fd 的变体),让内核:
- 把模块二进制拷进内核地址空间。
- 重定位、解析符号依赖(模块用到的内核导出符号
EXPORT_SYMBOL)。 - 调用模块的
init函数——init 返回 0 成功,返回非 0 则加载失败回滚。 - 注册到
/proc/modules、/sys/module/。
1 | 用户态 insmod foo.ko ──init_module() syscall──► 内核 |
面试金句:“insmod 把 .ko 动态加载进内核 ring0 地址空间、跑模块 init、常驻内核;它就是 init_module 系统调用的用户态壳。内核模块 = 编译进内核功能的’热插拔版’,不重启扩展内核。”
二、insmod vs modprobe
| insmod | modprobe | |
|---|---|---|
| 输入 | 单个 .ko 文件路径 |
模块名(不带 .ko、不带路径) |
| 依赖解析 | 不解析,缺依赖直接报错 | 自动从 /lib/modules/$(uname -r) 解析依赖、按序加载 |
| 来源 | 当前目录任意 .ko | modules.dep 索引的标准模块目录 |
| 用途 | 加载自己编的、没装到标准目录的 .ko | 加载发行版自带驱动(如 modprobe nfsd) |
- 你自己编译的 .ko(在当前目录)用
insmod ./hello.ko。 - 发行版已安装的模块用
modprobe <name>,它会读modules.dep把依赖一并装上。 insmod不会自动装依赖——若你的模块depends on别的模块,要先 insmod 依赖。modprobe解决这个。
辅助命令:
lsmod:列出已加载模块(读/proc/modules)。modinfo hello.ko:看模块元信息(license、version、依赖、parm 参数)。rmmod hello:卸载模块(调 exit)。dmesg | tail:看模块printk输出。
三、模块签名坑(最常见的"加载失败"原因)
启用了 Secure Boot 的系统(多数云主机/笔记本)内核要求模块有签名才能加载。报错形如:
1 | insmod: ERROR: could not insert module hello.ko: Required key not available |
因为 Secure Boot 链下,内核只信被 MOK(Machine Owner Key)签过的模块。解决:
- 生成自签名 MOK:
mokutil导入你的公钥到内核信任链(一次性,需重启确认)。 - 给
.ko签名:scripts/sign-file sha256 privkey.pem cert.der hello.ko。 - 或在测试机上关闭 Secure Boot(BIOS 里关)最省事。
另一类坑是 ABI/版本不匹配:.ko 是为某个内核版本编译的(用 vermagic 字符串标记),换内核版本加载会被拒(invalid module format / vermagic mismatch)。所以模块必须在当前运行的内核的 headers 下编译。容器/不同发行版交叉编译要特别注意 uname -r 对齐。
面试金句:“insmod 失败两大常见因:Secure Boot 要求模块签名(Required key not available,要 MOK 签名或关 Secure Boot);vermagic 不匹配(模块为别的内核版本编的,要当前内核 headers 重编)。”
四、实操:一个可跑通的 hello world 模块
在一台你能 sudo、且最好关了 Secure Boot的 Linux 测试机(VM 最方便)。生产机别玩。
4.1 装内核 headers(前提)
模块要用当前运行内核的 headers 编译:
1 | sudo apt-get update |
容器里编译内核模块坑多(容器不共享内核源码树、且容器内一般没内核 headers),建议在宿主机/VM 上做。
4.2 写模块 hello.c
1 | // hello.c — 最小可加载内核模块 |
要点:
MODULE_LICENSE("GPL")必填,否则用 GPL-only 导出符号会失败、内核被标记 tainted。__init/__exit提示编译器把这段放专用 section,加载后 init 段可释放省内存。module_param让模块接受参数,insmod hello.ko whom=World传值,可在/sys/module/hello/parameters/whom读。pr_info=printk(KERN_INFO ...),输出到内核环形缓冲区,用dmesg看。
4.3 写 Makefile
1 | # Makefile — 用内核 kbuild 体系编译模块 |
obj-m += hello.o告诉 kbuild 把hello.c编成模块hello.ko(文件名取自源文件名)。make -C $(KDIR) M=$(PWD) modules:进内核树跑 kbuild 的 modules 目标,在当前目录产出.ko。- 多文件模块用
hello-objs := a.o b.o。
4.4 编译
1 | make |
4.5 加载 + 验证 + 卸载
1 | # 1) 加载(默认 whom=World) |
到这一步,一个完整的"写模块→编译→insmod→rmmod"闭环就跑通了。
4.6 常见报错与排查
| 报错 | 原因 | 解决 |
|---|---|---|
Required key not available / PKCS7 signature not in .ko |
Secure Boot 要求签名 | 关 Secure Boot,或 MOK 签名 .ko(§三) |
Invalid module format / vermagic mismatch |
.ko 为别的内核版本编 |
装当前 uname -r 的 headers 重编 make |
Unknown symbol in module / dmesg disagrees about version of symbol |
用了未导出/版本不符的内核符号 | modinfo、nm 看依赖;只 EXPORT_SYMBOL 导出的符号才可用 |
Operation not permitted |
非 root / 容器无 CAP_SYS_MODULE |
sudo;容器要给 --privileged 或 CAP_SYS_MODULE(生产别给) |
| init 返回非 0 | 模块 init 失败 | 看 dmesg 自打日志,修逻辑 |
Module already in use(rmmod) |
模块被引用计数不为 0 | 先停掉使用它的进程/驱动,lsmod 看 Used by 列 |
排查命令:
1 | modinfo hello.ko # vermagic / depends / parm |
五、进阶:模块能做什么、安全提醒
模块能干的事(ring0 权限):
- 注册字符/块设备(
alloc_chrdev_region+cdev_add)—— 自定义设备驱动。 - 注册文件系统(
register_filesystem)、网络协议、netfilter hook。 - 实现
/proc//sys接口(proc_create/kobject_create)和用户态交互。 - hook 系统调用表 / kprobe(需
EXPORT_SYMBOL,部分被限制)。 - alcor-chaos 风格的内核态劫持/故障注入配合件。
安全提醒:
- 模块 = 完全信任的内核态代码,写错会 kernel panic、死机、数据损坏。生产加载要签名 + 审计。
- 容器给
CAP_SYS_MODULE等于给宿主内核权,生产容器绝不给。 - 别在生产机
insmod未审计.ko,等于执行任意 ring0 代码。 - 模块卸载前提是引用计数为 0;强行
rmmod --force可能崩。
面试金句:“内核模块有 ring0 全权,能注册设备/文件系统/netfilter、hook 系统调用,写错就 panic;所以生产模块必须签名+审计,容器绝不给 CAP_SYS_MODULE。insmod 本质是给内核执行任意 ring0 代码的入口,故权限管控是命门。”
六、面试速答清单
Q1:insmod 干什么、原理?
把
.ko内核模块动态加载进内核 ring0 地址空间。它调init_module/finit_module系统调用,内核拷贝二进制、重定位、解析EXPORT_SYMBOL依赖、调模块 init,成功则常驻。模块 = 编译进内核功能的"热插拔版",不重启扩展内核。
Q2:insmod 和 modprobe 区别?
insmod 加载单个
.ko、不解析依赖(缺依赖就报错),用于自己编的没装到标准目录的模块。modprobe 输入模块名、自动从/lib/modules/$(uname -r)的modules.dep解析依赖按序加载,用于发行版自带模块。卸载都可用rmmod,但 modprobe 有-r递归卸依赖。
Q3:insmod 报 Required key not available 怎么办?
Secure Boot 开启时内核只信 MOK 签过的模块。要么关 Secure Boot(测试机),要么生成 MOK 用
mokutil导入公钥、用内核scripts/sign-file给.ko签名。另一类invalid module format是 vermagic 不匹配——用当前uname -r的 headers 重编。
Q4:写一个最小内核模块要哪些要素?
#include <linux/module.h>、MODULE_LICENSE("GPL")(必填否则 tainted/符号失效)、module_init(fn)+module_exit(fn)、init 返回 0 成功否则回滚。可选module_param收参、__init/__exit段优化。Makefile 用 kbuild:obj-m += hello.o+make -C /lib/modules/$(uname -r)/build M=$(PWD) modules。
Q5:怎么验证模块加载成功?
lsmod | grep hello、cat /proc/modules看是否列出;dmesg | tail看 init 的pr_info输出;/sys/module/hello/parameters/看运行时参数。卸载rmmod hello后 exit 的 printk 也会进 dmesg。
Q6:内核模块有哪些安全风险?
模块跑在 ring0 拥有内核全权,能注册设备/文件系统/netfilter、hook syscall,写错 kernel panic、死机、数据损坏。所以生产模块必须签名+审计;容器给
CAP_SYS_MODULE等于给宿主内核任意执行权,生产容器绝不给。insmod 本质是"给内核注入任意 ring0 代码"的入口,权限管控是命门。
Q7:模块卸载失败 Module already in use?
模块引用计数不为 0(有进程/驱动在用)。
lsmod的 “Used by” 列看谁引用,先停掉使用者再rmmod。别用rmmod --force,可能崩。模块 init 里建的资源(proc/sysfs、设备号)要在 exit 里对应释放,否则卸载也失败。
七、一张图收口
1 | 编写: hello.c (MODULE_LICENSE + module_init/exit + pr_info) + Makefile (obj-m + kbuild) |
主线一句话:insmod 把 .ko 经 init_module 系统调用加载进内核 ring0、跑模块 init、常驻内核,是"不重启扩展内核"的热插拔入口;自己编的用 insmod(不解析依赖)、发行版用 modprobe(自动解析依赖);最大坑是 Secure Boot 要 MOK 签名、vermagic 要匹配当前内核 headers;模块有 ring0 全权,生产必须签名审计、容器绝不给 CAP_SYS_MODULE。 把加载原理、insmod/modprobe 区别、签名坑、实操全流程讲顺,insmod 这关就稳了。
参考资料
- Linux Kernel Module Programming Guide(
Documentation/admin-guide/modules.rst) - man:
insmod(8),modprobe(8),rmmod(8),lsmod(8),modinfo(8),init_module(2),finit_module(2) Documentation/admin-guide/module-signing.rst(模块签名)- 与本人《x86 体系结构基础》《操作系统接口》篇交叉对照


