4、内核模块的依赖与多文件编译
单个文件的内核模块写起来简单,但真实项目里没人这么干。你想想看,一个驱动动辄几千行,全塞在一个文件里,维护起来简直是噩梦。这一讲我们就聊聊多文件模块怎么写、依赖怎么处理,还有那个让你省心的 modprobe 工具。
4.1 多源文件模块的 Makefile 写法
先看一个实际例子。假设我有一个虚拟字符设备驱动,分成三个文件:
vchar_main.c—— 模块入口、file_operationsvchar_ioctl.c—— ioctl 命令处理vchar_sysfs.c—— sysfs 接口
对应的 Makefile 怎么写?
obj-m := vchar.o
vchar-objs := vchar_main.o vchar_ioctl.o vchar_sysfs.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
关键就两行:
obj-m := vchar.o—— 告诉内核构建系统,最终要生成vchar.kovchar-objs := ...—— 列出所有参与编译的.o文件
注意命名规则:模块名 vchar 和 vchar-objs 的前缀必须一致。我见过有人写成 vchar-objs := main.o ioctl.o,结果编译出来只有 vchar.ko 但符号全丢了,折腾半天才发现是命名不匹配。
vchar-objs := src/main.o src/ioctl.o,内核构建系统会自动处理。
4.2 模块依赖关系处理
模块之间也会有依赖。比如我的 vchar.ko 要用到另一个模块 core_helper.ko 里导出的函数。这时候需要做两件事。
4.2.1 导出符号
在 core_helper.c 里:
#include <linux/module.h>
int helper_do_work(int arg)
{
// 实际工作
return 0;
}
EXPORT_SYMBOL(helper_do_work);
EXPORT_SYMBOL 宏会把符号暴露给其他模块。不加这个宏,外部模块就看不到这个函数。
4.2.2 声明依赖
在 vchar_main.c 里:
#include <linux/module.h>
extern int helper_do_work(int arg);
MODULE_LICENSE("GPL");
MODULE_DEPEND("core_helper"); // 声明依赖
MODULE_DEPEND 不是标准内核宏,实际做法是在模块信息里声明。更常见的做法是让 modprobe 通过 depmod 生成的依赖文件自动处理。
vchar.ko 先加载,而 core_helper.ko 还没加载,insmod 会报 "Unknown symbol" 错误。手动用 insmod 时必须先加载被依赖的模块。
4.3 modprobe 工具的使用
modprobe 比 insmod 聪明得多。它会自动处理依赖、自动加载需要的其他模块。我个人习惯在开发板上调试时只用 modprobe,除非要指定特殊参数。
4.3.1 基本用法
| 命令 | 说明 |
|---|---|
modprobe vchar |
加载 vchar 模块及其依赖 |
modprobe -r vchar |
卸载 vchar 模块及其依赖(如果无其他模块使用) |
modprobe -c |
查看当前模块配置 |
modprobe --show-depends vchar |
显示 vchar 的依赖关系 |
4.3.2 depmod 是关键
modprobe 能自动处理依赖,靠的是 depmod 生成的 modules.dep 文件。每次编译安装新模块后,记得跑一遍:
depmod -a
这个命令会扫描 /lib/modules/$(uname -r)/ 下的所有 .ko 文件,解析符号引用关系,生成依赖数据库。没有这一步,modprobe 就不知道谁依赖谁。
/lib/modules/... → 执行 depmod -a → 使用 modprobe 加载。
4.3.3 模块参数传递
modprobe 也支持传参,语法和 insmod 一样:
modprobe vchar debug=1 max_devices=4
如果参数比较多,可以写在 /etc/modprobe.d/ 下的配置文件里:
# /etc/modprobe.d/vchar.conf
options vchar debug=1 max_devices=4
这样每次 modprobe vchar 都会自动带上这些参数。
4.4 知识结构总览
下面这张图把多文件编译、依赖处理和 modprobe 的关系串起来了:
4.5 实战避坑指南
最后分享几个我实际项目中遇到的坑:
- 符号冲突: 两个模块导出同名符号,加载时会报错。建议给导出函数加模块名前缀,比如
vchar_do_work而不是do_work。 - 循环依赖: 模块 A 依赖 B,B 又依赖 A。内核模块不允许循环依赖,必须重构代码把公共部分抽成第三个模块。
- modprobe 找不到模块: 多半是模块没装到正确路径,或者忘了跑
depmod -a。检查/lib/modules/$(uname -r)/下有没有你的.ko文件。 - 参数没生效: 如果通过配置文件传参,确认文件名以
.conf结尾,且放在/etc/modprobe.d/目录下。
嗯,多文件编译和依赖处理其实不难,关键是理解背后的机制。搞懂了这些,后面写复杂驱动时就能游刃有余了。