Linux内核模块编译加载教程:入门编写并运行你的第一个模块
为什么要自己编译内核模块?
内核模块是Linux系统动态加载进内核的一段代码,用来扩展功能而不需要重新编译整个内核。
比如添加新硬件驱动、文件系统支持或网络过滤等。
对于运维和开发者来说,掌握 Linux内核模块编译加载教程 是深入系统底层的第一步。
下面我会用最直接的步骤,带你从头写一个最简单的模块,并且让它跑起来。
第一步:准备好编译环境
编译内核模块需要三样东西:编译器(gcc)、构建工具(make)以及当前内核对应的开发包(kernel headers)。
1. 查看当前内核版本
uname -r
记住输出的版本号,比如 5.15.0-91-generic。
2. 安装编译工具和内核头文件
Debian/Ubuntu 系:
sudo apt update
sudo apt install build-essential linux-headers-$(uname -r)
CentOS/RHEL 系:
sudo yum install epel-release # 如果需要
sudo yum install kernel-devel kernel-headers
安装完成后,确认 /lib/modules/$(uname -r)/build 是一个有效链接。
如果提示找不到 linux-headers-$(uname -r),说明当前内核版本比较特殊(如自定义内核),需要手动安装匹配的头文件。这是新手最常见的第一个坑。
第二步:编写一个“Hello World”模块
新建一个文件夹,在里面创建两个文件:hello.c 和 Makefile。
hello.c 代码
#include
#include
#include
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World kernel module");
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, kernel module!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, kernel module!\n");
}
module_init(hello_init);
module_exit(hello_exit);
Makefile 配置
obj-m := hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
注意:Makefile 里 default: 和 clean: 前面的缩进必须用 Tab,不能用空格,否则编译会报错。
第三步:编译模块
make
如果一切顺利,你会看到大量输出,最终出现 Building modules, stage 2. 等提示。
当前目录下会生成 hello.ko 文件。
使用 ls -lh hello.ko 查看文件大小,正常应该在几KB到十几KB之间。
常见编译报错及解决
- /lib/modules/xxx/build: No such file or directory:忘记安装内核头文件,或者安装的版本与当前内核不匹配。重新安装正确的
linux-headers-$(uname -r)。 - implicit declaration of function:代码里漏掉了头文件,或者使用了旧版内核不支持的函数。检查
hello.c中的包含。 - make: * /lib/modules/xxx/build: 权限不足**:
sudo make试试,或者检查文件夹权限。
第四步:加载、查看和卸载模块
加载模块
sudo insmod hello.ko
没有消息就是最好的消息。
如果提示 insmod: ERROR: could not insert module hello.ko: Invalid module format,说明编译时的内核版本与当前运行内核不匹配,请确认 uname -r 一致。
查看模块是否加载成功
lsmod | grep hello
如果显示类似 hello 16384 0,说明模块已在内存中。
查看模块输出信息(dmesg)
dmesg | tail -10
你会看到一行:[ xxx.xxxxxx] Hello, kernel module!。
如果信息太多,可以清空后重新查看:sudo dmesg -c 再加载一次。
卸载模块
sudo rmmod hello
再次 dmesg | tail -3,你会看到 Goodbye, kernel module!。
第五步:避坑指南与验证技巧
常遇到的坑
- 内核版本严格匹配:编译的内核模块只能在相同版本(甚至相同 .config)的内核上加载。升级内核后必须重新编译。
- 符号导出冲突:如果模块使用了其他模块未导出的符号,加载时会报
Unknown symbol。解决方法是用depmod确保依赖关系。 - 函数声明过时:在内核 5.10+ 中,一些旧版接口(如
__init位置)已变化,多参考当前内核Documentation。 - 安全启动 (Secure Boot):有些系统开启了 UEFI 安全启动,会阻止加载未签名模块。可以临时关闭或给模块签名。
如何验证模块真正生效
除了 dmesg 打印信息,你还可以让模块做一些可见的操作,例如修改内核参数。
但作为初学者,能用 lsmod 看到它、用 dmesg 看到输出,就已经成功走通了 Linux内核模块编译加载教程 的全流程。
你现在可以修改 hello.c 里的 printk 内容,重新编译加载,体验迭代过程。
所有命令和路径都已在上面给出,零基础可以直接复制运行。
如果你遇到任何报错,先对照本文的避坑部分检查版本和权限,大多数问题都能解决。