Linux 内核模块(Kernel Module)是动态加载到内核中的代码片段,用于扩展内核功能,而 .ko 文件就是编译后的内核模块文件,生成 .ko 文件需要准备开发环境、编写模块代码、配置 Makefile 并通过编译工具完成,以下是详细步骤和说明。
环境准备
在开始生成 .ko 文件前,需确保系统已安装必要的开发工具和内核开发包,具体依赖因 Linux 发行版不同而略有差异:
安装基础编译工具
包括 GCC 编译器、Make 构建工具等,以 Ubuntu/Debian 为例:
sudo apt update sudo apt install build-essential
以 CentOS/RHEL 为例:
sudo yum groupinstall "Development Tools"
安装内核开发包
内核开发包提供了编译模块所需的头文件(如 linux/module.h、linux/init.h 等),需与当前运行内核版本匹配,可通过以下命令查看当前内核版本:
uname -r # 5.15.0-88-generic
对应安装开发包:
- Ubuntu/Debian:
sudo apt install linux-headers-$(uname -r)
- CentOS/RHEL:
sudo yum install kernel-devel-$(uname -r)
若需编译其他内核版本的模块,需下载对应版本的内核源码并配置,此处以当前运行内核为例。
编写内核模块代码
内核模块代码通常包含模块初始化函数、模块退出函数及必要的宏声明,以下以一个简单的字符设备驱动模块为例(hello.c):
#include <linux/init.h> // module_init, module_exit 宏 #include <linux/module.h> // MODULE_LICENSE, MODULE_AUTHOR 等宏 #include <linux/kernel.h> // printk 函数 // 模块初始化函数(模块加载时调用) static int __init hello_init(void) { printk(KERN_INFO "Hello, kernel module loaded!n"); return 0; // 返回 0 表示成功 } // 模块退出函数(模块卸载时调用) static void __exit hello_exit(void) { printk(KERN_INFO "Hello, kernel module unloaded!n"); } // 注册初始化和退出函数 module_init(hello_init); module_exit(hello_exit); // 模块许可证(必须声明,否则加载时会警告) MODULE_LICENSE("GPL"); // 模块作者信息(可选) MODULE_AUTHOR("Your Name"); // 模块描述(可选) MODULE_DESCRIPTION("A simple Hello World kernel module");
代码说明:
module_init
和module_exit
:分别指定模块加载和卸载时调用的函数,__init
和__exit
是宏修饰符,__init
会在模块加载后释放内存,__exit
仅在卸载时有效。printk
:内核中的打印函数,KERN_INFO
为日志级别,可通过dmesg
查看输出。MODULE_LICENSE("GPL")
:必须声明许可证,否则加载模块时内核会提示 “module verification failed: signature and/or required key missing – tainting kernel”。
编写 Makefile
编译内核模块依赖 Makefile,其核心是指定模块目标、内核源码路径及编译规则,以下为与 hello.c 对应的 Makefile 示例:
obj-m += hello.o # 指定编译目标为 hello.ko(hello.o 对应 .ko 文件) all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Makefile 说明:
obj-m += hello.o
:表示将 hello.c 编译为内核模块,目标文件为 hello.ko,若模块由多个文件组成(如 hello.c 和 helper.c),需改为obj-m += hello.o
并在下一行hello-objs := helper.o
。make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
:-C
:切换到内核源码目录(通常是/lib/modules/$(uname -r)/build
,该目录指向内核头文件和 Makefile)。M=$(PWD)
:指定当前模块源码目录路径,内核编译系统会在此目录查找模块源码。modules
:目标,表示编译内核模块。
clean
:清理编译生成的中间文件(如 .o、.mod.c、.ko 等)。
编译生成 .ko 文件
在模块源码目录下执行 make
命令,即可生成 .ko 文件:
make
编译成功后,目录中会生成多个文件,hello.ko 即为目标内核模块文件:
文件名 | 说明 |
---|---|
hello.o | 模块的目标文件(未链接的 ELF 文件) |
hello.ko | 最终的内核模块文件(可动态加载到内核) |
hello.mod.c | 模块描述信息文件,包含模块的元数据(如作者、许可证等) |
hello.mod.o | 模块描述信息的目标文件 |
Module.symvers | 符号版本信息,用于模块与内核版本的兼容性检查 |
modules.order | 模块编译顺序记录文件 |
若编译失败,可通过错误信息排查问题,常见原因包括:
- 内核头文件版本不匹配:确保安装的
linux-headers-$(uname -r)
与运行内核版本一致。 - 代码语法错误:检查内核模块特有的函数和宏是否正确使用(如
printk
的日志级别)。 - Makefile 书写错误:确认
obj-m
的目标名与源文件名对应,路径是否正确。
加载与测试模块
生成 .ko 文件后,需加载到内核中测试功能,常用命令如下:
加载模块
sudo insmod ./hello.ko # 加载模块
查看模块加载状态
lsmod | grep hello # 查看 hello 模块是否已加载
查看内核日志
模块中的 printk
输出可通过 dmesg
查看:
dmesg | tail -n 5 # 查看最近 5 条内核日志,应包含 "Hello, kernel module loaded!"
卸载模块
sudo rmmod hello # 卸载模块(无需后缀 .ko)
验证卸载
再次查看内核日志,应输出 “Hello, kernel module unloaded!”:
dmesg | tail -n 5
常用模块操作命令
命令 | 作用 |
---|---|
insmod |
加载指定的 .ko 文件到内核 |
rmmod |
从内核中卸载已加载的模块(需指定模块名,无后缀) |
lsmod |
列出当前已加载的内核模块 |
modinfo |
查看模块的元信息(如许可证、作者、描述等) |
dmesg |
查看内核日志,包含模块加载/卸载的输出信息 |
常见问题及解决
编译时报错 “fatal error: linux/module.h: No such file or directory”
原因:未安装内核开发包或内核头文件路径错误。
解决:确保已安装 linux-headers-$(uname -r)
,并通过 ls /lib/modules/$(uname -r)/build
检查路径是否存在。
加载模块时报错 “Module verification failed: signature and/or required key missing”
原因:未声明模块许可证或许可证不被内核认可(如 “Proprietary”)。
解决:在代码中添加 MODULE_LICENSE("GPL")
或其他许可证(如 “MIT”),确保与内核许可证兼容。
相关问答 FAQs
Q1:如何查看内核模块的依赖关系?
A1:可通过 modinfo
命令查看模块的依赖符号,或使用 lsmod
结合 /proc/modules
分析模块依赖,查看 hello 模块的符号信息:
modinfo hello.ko
输出中的 depends
字段会列出模块依赖的其他模块(若无依赖则为空)。
Q2:交叉编译内核模块时如何指定架构?
A2:交叉编译时需设置 ARCH
和 CROSS_COMPILE
变量,并在 Makefile 中指定交叉编译工具链,在 ARM 架构下编译:
export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- make -C /path/to/arm-kernel-source M=$(PWD) modules
/path/to/arm-kernel-source
为 ARM 内核源码目录,需确保与目标平台的内核版本匹配。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/15080.html