2. 内核编译与模块机制
内核编译配置与模块机制是设备驱动开发的基石。本章将深入讲解 Kconfig/Makefile 的构建规则、模块的加载与卸载操作、模块参数的传递方式以及模块间的依赖管理。掌握这些内容后,你就能独立完成一个驱动模块的编译、加载与调试全流程。
2.1 内核编译配置系统:Kconfig 与 Makefile
Linux 内核使用 Kconfig 定义配置选项,使用 Makefile 描述编译规则。两者配合实现“配置-编译-生成”的自动化构建流程。
2.1.1 Kconfig 语法与配置项
Kconfig 文件位于每个驱动目录下,用于声明该驱动的配置选项。一个典型的 Kconfig 条目如下:
config HELLO_DRIVER
tristate "Hello World Driver"
depends on ARM
default n
help
This is a sample driver for learning module mechanism.
Say Y to compile it into kernel, M to build as module.
关键字段说明:
| 字段 | 含义 | 取值示例 |
|---|---|---|
tristate |
三态选项:Y(编译进内核)、M(编译为模块)、N(不编译) | tristate "Hello Driver" |
bool |
布尔选项:Y 或 N,不能编译为模块 | bool "Enable Debug" |
depends on |
依赖其他配置项 | depends on ARM && !SMP |
select |
反向选择,自动选中其他配置 | select GPIO |
default |
默认值 | default y 或 default n |
tristate 是最常用的类型。它允许用户将驱动编译为模块(M),便于开发阶段反复加载测试,无需每次修改都重新编译整个内核。
2.1.2 Makefile 编译规则
内核 Makefile 使用 obj-y、obj-m、obj-$(CONFIG_xxx) 三种变量控制编译行为:
# 直接编译进内核
obj-y += hello.o
# 编译为可加载模块
obj-m += hello.o
# 根据 Kconfig 配置决定
obj-$(CONFIG_HELLO_DRIVER) += hello.o
# 多文件模块
hello-objs := main.o utils.o
编译流程关系图:
2.2 模块编译实战
编写一个最简单的内核模块,并完成编译与加载。这是驱动开发的“Hello World”。
2.2.1 模块源码模板
// hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
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);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World module");
2.2.2 外部模块 Makefile
当驱动源码不在内核源码树内时,使用以下 Makefile 进行外部编译:
# Makefile for external module
obj-m += hello.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
-C $(KDIR) 切换到内核构建目录,M=$(PWD) 指定模块源码路径。编译完成后生成 hello.ko 文件。
2.3 模块加载与卸载:insmod / rmmod / lsmod
模块编译为 .ko 文件后,通过以下命令进行生命周期管理:
| 命令 | 功能 | 示例 | 注意事项 |
|---|---|---|---|
insmod |
加载模块 | insmod hello.ko |
需要绝对路径或相对路径,不自动解决依赖 |
rmmod |
卸载模块 | rmmod hello |
模块被使用时无法卸载(refcount > 0) |
lsmod |
列出已加载模块 | lsmod | grep hello |
显示模块大小、被谁使用、依赖关系 |
modprobe |
智能加载(推荐) | modprobe hello |
自动解决依赖,从 /lib/modules 查找 |
modinfo |
查看模块信息 | modinfo hello.ko |
显示作者、描述、许可证、参数等 |
insmod 直接加载当前目录的 .ko 文件,避免 modprobe 从系统目录加载旧版本。卸载时如果提示 Module is in use,说明有其他模块或进程正在引用该模块,需要先解除引用。
2.4 模块参数传递
内核模块支持在加载时传递参数,无需重新编译即可调整驱动行为。使用 module_param 宏声明参数:
// param_demo.c
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
static int count = 1;
static char *name = "default";
static int arr[3] = {0, 0, 0};
module_param(count, int, 0644);
module_param(name, charp, 0644);
module_param_array(arr, int, NULL, 0644);
MODULE_PARM_DESC(count, "Number of times to print");
MODULE_PARM_DESC(name, "Name to display");
MODULE_PARM_DESC(arr, "Array of three integers");
static int __init param_init(void)
{
int i;
for (i = 0; i < count; i++)
printk(KERN_INFO "Hello %s, arr[0]=%d arr[1]=%d arr[2]=%d\n",
name, arr[0], arr[1], arr[2]);
return 0;
}
module_init(param_init);
module_exit(/* ... */);
加载时传递参数:
# 传递标量参数
insmod param_demo.ko count=5 name="driver"
# 传递数组参数
insmod param_demo.ko arr=10,20,30
# 查看参数(通过 sysfs)
cat /sys/module/param_demo/parameters/count
| 参数类型 | 宏 | sysfs 权限 | 说明 |
|---|---|---|---|
| int | module_param(name, int, perm) |
0644 | 整型参数 |
| charp | module_param(name, charp, perm) |
0444 | 字符串指针 |
| bool | module_param(name, bool, perm) |
0644 | 布尔值(y/n/1/0) |
| 数组 | module_param_array(name, type, &num, perm) |
0644 | 数组参数,num 返回实际元素个数 |
echo 修改参数值(需模块支持运行时更新)。调试阶段建议设为 0644 方便动态调整。
2.5 模块依赖管理
模块之间可能存在依赖关系。例如,USB 驱动依赖 USB 核心模块,I2C 设备驱动依赖 I2C 总线驱动。内核通过 MODULE_DEPEND 和符号导出机制管理依赖。
2.5.1 符号导出
一个模块可以通过 EXPORT_SYMBOL 导出函数或变量,供其他模块使用:
// exporter.c
#include <linux/module.h>
void my_helper_function(void)
{
printk(KERN_INFO "Helper function called\n");
}
EXPORT_SYMBOL(my_helper_function);
// user.c
extern void my_helper_function(void);
static int __init user_init(void)
{
my_helper_function(); // 调用导出函数
return 0;
}
2.5.2 依赖关系查看
使用 lsmod 查看模块依赖:
$ lsmod
Module Size Used by
usb_storage 73728 2
usbcore 286720 4 usb_storage,ehci_hcd,ohci_hcd
scsi_mod 245760 2 usb_storage,sd_mod
第三列 Used by 显示依赖该模块的其他模块数量及名称。卸载时必须先卸载依赖方,再卸载被依赖方。
2.5.3 自动处理依赖:depmod
depmod 命令扫描所有已安装模块,生成 modules.dep 文件,记录模块间的依赖关系:
# 更新依赖数据库
depmod -a
# 查看某个模块的依赖
modprobe --show-depends hello
# 依赖文件内容示例(/lib/modules/$(uname -r)/modules.dep)
kernel/drivers/usb/storage/usb-storage.ko: kernel/drivers/scsi/scsi_mod.ko kernel/drivers/usb/core/usbcore.ko
modprobe 可以自动处理依赖顺序,但开发调试时建议手动 insmod 以精确控制加载流程。
2.6 本章小结
- Kconfig 定义配置选项,Makefile 定义编译规则,两者配合实现灵活的内核构建。
- 模块编译使用
obj-m变量,外部模块通过-C $(KDIR) M=$(PWD)方式编译。 insmod加载模块,rmmod卸载模块,lsmod查看模块状态。module_param支持在加载时传递参数,通过 sysfs 可在运行时查看或修改。EXPORT_SYMBOL导出符号供其他模块使用,depmod管理依赖关系。
掌握这些基础机制后,下一阶段将进入字符设备驱动的核心实现——file_operations 接口与设备节点创建。