3. Linux内核模块基础
在 Android 驱动开发中,Linux 内核模块(Kernel Module)是实现 HAL 层与硬件交互的底层基石。模块允许开发者在不重新编译整个内核的情况下,动态扩展内核功能。本章将深入剖析模块的加载/卸载机制、参数传递方法以及依赖关系管理,为后续编写实际的 HAL 与 Kernel 联动代码打下坚实基础。
3.1 模块加载与卸载:insmod / rmmod / modprobe
Linux 提供了三个核心工具来管理内核模块的生命周期。下表对比了它们的功能与适用场景:
| 命令 | 功能 | 依赖处理 | 典型用法 |
|---|---|---|---|
| insmod | 直接加载指定的 .ko 文件到内核 | 不自动解决依赖 | insmod my_driver.ko |
| rmmod | 从内核卸载指定的模块 | 不检查依赖模块 | rmmod my_driver |
| modprobe | 智能加载/卸载,自动处理依赖 | 自动加载依赖模块 | modprobe my_driver |
模块的生命周期由两个关键函数控制:module_init() 和 module_exit()。当执行 insmod 时,内核调用 module_init 指定的初始化函数;执行 rmmod 时,调用 module_exit 指定的清理函数。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init my_driver_init(void)
{
printk(KERN_INFO "MyDriver: Module loaded successfully.\n");
// 在此处注册设备、分配资源等
return 0;
}
static void __exit my_driver_exit(void)
{
printk(KERN_INFO "MyDriver: Module unloaded.\n");
// 在此处释放资源、注销设备等
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Android Driver Developer");
MODULE_DESCRIPTION("A sample kernel module for HAL-Kernel linkage");
adb shell 进入目标系统后,通过 insmod 或 modprobe 加载模块。使用 lsmod 命令可以查看当前已加载的所有模块及其使用计数。
3.2 模块参数传递
内核模块支持在加载时通过命令行传递参数,这使得驱动可以在不重新编译的情况下调整行为。参数使用 module_param() 宏声明,支持多种数据类型。
支持的参数类型:
| 类型宏 | C 数据类型 | 示例 |
|---|---|---|
module_param(name, int, perm) |
int |
调试级别、超时值 |
module_param(name, charp, perm) |
char * |
设备路径、名称 |
module_param(name, bool, perm) |
bool |
启用/禁用特性 |
module_param_array(name, type, num, perm) |
数组 | 多通道配置 |
参数权限位(perm):
0:参数仅在/sys/module/中不可见,仅用于加载时传递。S_IRUGO:所有用户可读(通常用于调试参数)。S_IWUSR | S_IRUGO:所有者可写,其他用户可读。
static int debug_level = 0;
static char *device_name = "default_dev";
module_param(debug_level, int, S_IRUGO | S_IWUSR);
module_param(device_name, charp, S_IRUGO);
MODULE_PARM_DESC(debug_level, "Debug output level (0=off, 1=info, 2=verbose)");
MODULE_PARM_DESC(device_name, "Name of the hardware device");
static int __init param_demo_init(void)
{
printk(KERN_INFO "ParamDemo: debug_level=%d, device_name=%s\n",
debug_level, device_name);
return 0;
}
加载时传递参数:
# 使用 insmod
insmod param_demo.ko debug_level=2 device_name="sensor0"
# 使用 modprobe(需要将参数写入 /etc/modprobe.d/ 或命令行)
modprobe param_demo debug_level=1 device_name="sensor1"
/sys/module/ 下的参数文件可能被非特权进程读取。
3.3 模块依赖关系
在实际的 Android 驱动栈中,一个驱动模块往往依赖于其他模块提供的符号(函数、变量)。例如,一个 I2C 传感器驱动依赖于 I2C 核心模块提供的总线操作函数。依赖关系通过 EXPORT_SYMBOL 和 EXPORT_SYMBOL_GPL 宏建立。
依赖关系建立流程:
- 导出符号:提供服务的模块使用
EXPORT_SYMBOL(func_name)将符号导出到内核符号表。 - 引用符号:依赖模块在代码中直接调用这些导出的函数。
- 链接时记录:编译依赖模块时,链接器记录未解析的符号引用。
- 加载时解析:
modprobe读取/lib/modules/$(uname -r)/modules.dep文件,自动按顺序加载所有依赖模块。
SVG 图:模块依赖关系与加载流程
3.4 实战:编写一个带参数和依赖的模块
以下示例展示了一个模拟的“虚拟传感器”模块,它依赖于一个“虚拟总线”模块,并接收加载时参数。
步骤 1:编写总线模块(bus_module.c)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
void virtual_bus_write(int data)
{
printk(KERN_INFO "VirtualBus: Writing data %d\n", data);
}
EXPORT_SYMBOL(virtual_bus_write);
static int __init bus_init(void)
{
printk(KERN_INFO "VirtualBus: Module loaded.\n");
return 0;
}
static void __exit bus_exit(void)
{
printk(KERN_INFO "VirtualBus: Module unloaded.\n");
}
module_init(bus_init);
module_exit(bus_exit);
MODULE_LICENSE("GPL");
步骤 2:编写传感器模块(sensor_module.c)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
extern void virtual_bus_write(int data);
static int sensor_id = 0;
module_param(sensor_id, int, S_IRUGO);
MODULE_PARM_DESC(sensor_id, "Sensor ID (0-255)");
static int __init sensor_init(void)
{
printk(KERN_INFO "Sensor: Initializing sensor ID %d\n", sensor_id);
virtual_bus_write(sensor_id); // 调用总线模块导出的函数
return 0;
}
static void __exit sensor_exit(void)
{
printk(KERN_INFO "Sensor: Module unloaded.\n");
}
module_init(sensor_init);
module_exit(sensor_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Sensor driver depending on virtual bus");
步骤 3:编译与加载测试
# 编译两个模块(假设已有 Makefile)
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 正确加载顺序(使用 modprobe 自动处理)
sudo modprobe sensor_module sensor_id=5
# 或者手动加载
sudo insmod bus_module.ko
sudo insmod sensor_module.ko sensor_id=5
# 查看加载状态
lsmod | grep -E "bus_module|sensor_module"
# 卸载(注意顺序)
sudo rmmod sensor_module
sudo rmmod bus_module
/vendor/lib/modules/ 或 /system/lib/modules/。使用 adb root 后,通过 adb shell insmod /vendor/lib/modules/xxx.ko 加载。对于依赖关系,建议使用 modprobe 并确保 modules.dep 文件已正确生成。
3.5 常见问题与避坑指南
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
insmod: ERROR: could not insert module |
模块与当前内核版本不匹配,或缺少依赖 | 检查 uname -r,重新编译模块;使用 modprobe 自动解决依赖 |
Unknown symbol 错误 |
依赖模块未加载,或符号未正确导出 | 确认依赖模块已加载;检查提供方是否使用了 EXPORT_SYMBOL |
rmmod: Module xxx is in use |
其他模块或进程正在使用该模块 | 先卸载依赖该模块的其他模块;检查 lsmod 的使用计数 |
| 模块参数未生效 | 参数权限为 0,或参数名拼写错误 | 设置 perm 为非零值;检查 /sys/module/<name>/parameters/ 下的文件 |
CONFIG_DEBUG_KERNEL 等调试选项。
掌握模块的加载、卸载、参数传递和依赖管理,是编写稳定、可维护的 Android 内核驱动的基础。下一阶段,我们将把这些知识应用到实际的 HAL 层与 Kernel 模块的联动通信中。