2. Linux内核模块编程基础:模块初始化/退出函数、module_init/module_exit宏、模块参数与许可证
Linux内核模块是动态加载到内核中的代码片段,无需重新编译整个内核即可扩展功能。理解模块编程基础是编写设备驱动的第一步。本节将深入剖析模块的生命周期、关键宏定义、参数传递机制以及许可证声明规范。
2.1 模块生命周期与核心函数
每个内核模块都有明确的加载和卸载阶段,分别由初始化函数和退出函数处理。这两个函数通过特定的宏注册到内核模块框架中。
2.1.1 模块初始化函数
初始化函数是模块的入口点,通常命名为 xxx_init 或 xxx_module_init。函数原型必须返回 int 类型,成功返回 0,失败返回负的错误码。
static int __init my_driver_init(void)
{
printk(KERN_INFO "My driver loaded successfully.\n");
/* 在此处进行硬件初始化、注册设备等操作 */
return 0;
}
关键字 __init 是一个编译器属性宏,告诉内核该函数仅在初始化阶段使用,初始化完成后可以释放其占用的内存。
2.1.2 模块退出函数
退出函数负责撤销初始化函数所做的一切。函数原型返回 void,通常命名为 xxx_exit 或 xxx_module_exit。
static void __exit my_driver_exit(void)
{
printk(KERN_INFO "My driver unloaded.\n");
/* 在此处释放资源、注销设备等操作 */
}
关键字 __exit 与 __init 对应,如果模块被编译进内核(非动态加载),该函数会被丢弃以节省内存。
2.1.3 module_init / module_exit 宏
这两个宏是模块与内核模块加载器之间的桥梁。它们将初始化函数和退出函数的地址注册到内核的模块表中。
module_init(my_driver_init);
module_exit(my_driver_exit);
下表总结了初始化与退出函数的对比:
| 特性 | 初始化函数 | 退出函数 |
|---|---|---|
| 执行时机 | 模块加载时(insmod) | 模块卸载时(rmmod) |
| 返回值 | int(0成功,负值失败) | void |
| 关键字 | __init | __exit |
| 注册宏 | module_init() | module_exit() |
| 主要职责 | 资源分配、设备注册 | 资源释放、设备注销 |
2.2 模块加载/卸载流程 SVG 图
下图展示了从用户空间执行 insmod/rmmod 命令到内核模块初始化/退出函数被调用的完整流程:
2.3 模块参数
模块参数允许用户在加载模块时传递配置值,无需重新编译。通过 module_param 宏声明参数,用户可以在 insmod 命令行中指定。
2.3.1 参数声明宏
#include <linux/moduleparam.h>
static int irq_num = 10;
static char *device_name = "my_i2c_dev";
module_param(irq_num, int, 0644);
module_param(device_name, charp, 0644);
参数说明:
- 第一个参数:变量名,必须与前面定义的全局变量一致。
- 第二个参数:数据类型,支持
int、long、charp(字符串指针)、bool等。 - 第三个参数:文件权限掩码,用于
/sys/module/<module>/parameters/下的 sysfs 文件权限。0表示不创建 sysfs 条目。
2.3.2 加载时传递参数
# insmod my_i2c_driver.ko irq_num=33 device_name="i2c-1"
charp 传递时,内核会复制字符串指针,确保字符串在模块生命周期内有效。建议使用静态字符串或 kstrdup 分配。
2.3.3 参数描述宏
使用 MODULE_PARM_DESC 为参数添加描述信息,便于用户查看:
MODULE_PARM_DESC(irq_num, "IRQ number for the device");
MODULE_PARM_DESC(device_name, "Device name string");
2.4 模块许可证与作者声明
Linux内核要求所有模块声明许可证,以维护GPL协议的合规性。未声明或使用非GPL兼容许可证的模块将无法使用某些内核导出的符号(EXPORT_SYMBOL_GPL)。
2.4.1 常用许可证宏
| 宏 | 许可证类型 | 说明 |
|---|---|---|
MODULE_LICENSE("GPL") |
GNU General Public License v2 | 最常用,可访问所有GPL导出的符号 |
MODULE_LICENSE("GPL v2") |
GPL v2 | 与"GPL"等价 |
MODULE_LICENSE("GPL and additional rights") |
GPL + 附加权利 | 允许额外条款 |
MODULE_LICENSE("Dual BSD/GPL") |
BSD 或 GPL | 双许可证 |
MODULE_LICENSE("Proprietary") |
专有许可证 | 无法使用GPL导出的符号 |
2.4.2 其他元信息宏
MODULE_AUTHOR("Your Name <email@example.com>");
MODULE_DESCRIPTION("I2C device driver for sensor XYZ");
MODULE_VERSION("1.0");
MODULE_ALIAS("i2c:my_sensor");
MODULE_LICENSE("GPL") 除非你有明确的商业许可需求。许多内核核心功能(如中断处理、内存分配)的符号都标记为 EXPORT_SYMBOL_GPL,非GPL模块无法调用。
2.5 完整的最小模块示例
以下是一个完整的、可编译的内核模块,集成了本节所有知识点:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
static int irq = 17;
static char *name = "default_dev";
module_param(irq, int, 0644);
module_param(name, charp, 0644);
MODULE_PARM_DESC(irq, "IRQ number for the device");
MODULE_PARM_DESC(name, "Device name string");
static int __init my_module_init(void)
{
printk(KERN_INFO "Module loaded: irq=%d, name=%s\n", irq, name);
/* 实际驱动中在此处注册I2C设备、申请中断等 */
return 0;
}
static void __exit my_module_exit(void)
{
printk(KERN_INFO "Module unloaded: cleaning up resources\n");
/* 实际驱动中在此处释放中断、注销设备等 */
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Linux Engineer <engineer@example.com>");
MODULE_DESCRIPTION("Minimal kernel module example with parameters");
MODULE_VERSION("1.0");
2.6 模块编程常见错误与避坑
- 忘记 module_init/module_exit:模块加载成功但初始化函数未执行,功能完全失效。
- 初始化函数返回非零值:模块加载失败,但已分配的资源未释放,导致资源泄漏。
- 退出函数未完全清理:卸载后设备仍被占用,下次加载时冲突。
- 参数类型不匹配:声明为
int却传递字符串,导致内核崩溃。 - 许可证声明缺失:内核打印警告,且无法使用 GPL 导出的符号。
- 使用 __init 但函数被多次调用:__init 函数在初始化后可能被释放,再次调用导致非法访问。
dmesg 查看 printk 输出;使用 lsmod 查看模块加载状态;使用 modinfo 查看模块元信息;使用 cat /sys/module/<module>/parameters/<param> 查看运行时参数值。
2.7 模块参数与 sysfs 交互 SVG 图
下图展示了模块参数如何在用户空间通过 sysfs 文件系统进行读写:
2.8 小结
本节覆盖了Linux内核模块编程的核心基础:
- 初始化/退出函数:模块的入口和出口,使用
__init/__exit优化内存。 - module_init/module_exit:注册函数到内核模块框架。
- 模块参数:通过
module_param实现运行时配置,支持 sysfs 动态调整。 - 许可证与元信息:使用
MODULE_LICENSE等宏声明模块属性。