3. Linux 内核模块基础:内核模块概念、模块加载与卸载、module_init 与 module_exit 宏、模块许可证与作者信息
在嵌入式 Linux 与 Android 内核开发中,内核模块(Kernel Module)是动态扩展内核功能的核心机制。它允许开发者在不重新编译整个内核、不重启设备的情况下,向运行中的内核插入或移除代码。本章将系统讲解内核模块的基本概念、生命周期管理、入口/出口宏定义以及许可证与作者信息的规范写法,为后续 GPIO 驱动开发奠定基础。
3.1 内核模块概念
内核模块是一段可以在运行时被加载到内核地址空间的代码,通常用于实现设备驱动、文件系统、网络协议等。与直接编译进内核(built-in)相比,模块化具有以下优势:
- 动态加载/卸载:无需重启系统即可添加或移除功能。
- 节省内存:仅在需要时加载模块,减少内核映像体积。
- 快速迭代:开发阶段可频繁修改、编译、加载模块,无需烧录整个内核。
- 热插拔支持:配合 udev 或设备树,可自动加载对应驱动模块。
💡 提示:在 Android 内核中,大部分硬件驱动(包括 GPIO、I2C、SPI 等)都以模块形式存在,存放在
/vendor/lib/modules/ 或 /system/lib/modules/ 目录下。
3.2 模块加载与卸载
内核模块的生命周期由用户空间工具和内核协同管理。常用的命令如下:
| 命令 | 功能 | 示例 |
|---|---|---|
insmod |
加载指定 .ko 模块文件,不自动处理依赖 | insmod gpio_driver.ko |
rmmod |
卸载指定模块,需确保无其他模块依赖 | rmmod gpio_driver |
modprobe |
智能加载/卸载,自动处理模块依赖 | modprobe gpio_driver |
lsmod |
列出当前已加载的所有模块 | lsmod | grep gpio |
modinfo |
查看模块的详细信息(作者、描述、参数等) | modinfo gpio_driver.ko |
⚠️ 注意:在 Android 设备上,由于
selinux 策略限制,普通应用无法直接执行 insmod,通常需要 root 权限或通过 vendor_init 脚本在 init 阶段加载。
3.3 module_init 与 module_exit 宏
每个内核模块必须定义两个核心宏:module_init() 和 module_exit(),分别指定模块加载和卸载时调用的函数。这两个宏是模块与内核之间的“契约”。
3.3.1 宏定义与函数签名
| 宏 | 触发时机 | 函数返回值 | 函数参数 |
|---|---|---|---|
module_init(x) |
执行 insmod 或 modprobe 时 |
int(0 成功,负值失败) |
无 |
module_exit(x) |
执行 rmmod 或 modprobe -r 时 |
void |
无 |
3.3.2 标准模块代码框架
#include <linux/module.h> // 所有模块必须包含
#include <linux/kernel.h> // 提供 printk 等内核打印函数
#include <linux/init.h> // 提供 __init 和 __exit 宏
static int __init gpio_driver_init(void)
{
printk(KERN_INFO "GPIO Driver: 模块加载成功\n");
// 此处后续将放置 GPIO 注册、中断申请等代码
return 0;
}
static void __exit gpio_driver_exit(void)
{
printk(KERN_INFO "GPIO Driver: 模块卸载成功\n");
// 此处后续将放置 GPIO 释放、中断注销等代码
}
module_init(gpio_driver_init);
module_exit(gpio_driver_exit);
🔑 关键点:
__init标记:提示内核该函数仅在初始化阶段使用,加载完成后可释放占用的内存。__exit标记:提示内核该函数仅在模块卸载时使用,若模块被编译进内核(built-in),此函数会被忽略以节省空间。printk是内核空间的打印函数,KERN_INFO表示日志级别,可通过dmesg查看输出。
3.4 模块许可证与作者信息
Linux 内核采用 GPL 许可证,内核模块必须声明其许可证类型,否则内核会认为该模块是“污染”内核(tainted kernel),并限制某些 GPL-only 符号的调用。此外,添加作者和描述信息有助于维护和调试。
3.4.1 常用许可证宏
| 宏 | 含义 | 是否允许调用 GPL-only 符号 |
|---|---|---|
MODULE_LICENSE("GPL") |
GNU General Public License v2 | ✅ 是 |
MODULE_LICENSE("GPL v2") |
GPL v2 版本 | ✅ 是 |
MODULE_LICENSE("GPL and additional rights") |
GPL 附加额外权利 | ✅ 是 |
MODULE_LICENSE("Dual BSD/GPL") |
双许可证:BSD 或 GPL | ✅ 是 |
MODULE_LICENSE("Proprietary") |
专有许可证 | ❌ 否(内核标记为 tainted) |
3.4.2 作者与描述信息宏
MODULE_AUTHOR("Your Name <your.email@example.com>");
MODULE_DESCRIPTION("GPIO Driver for Android Kernel Project");
MODULE_VERSION("1.0");
MODULE_ALIAS("platform:gpio-driver");
📌 最佳实践:
- 商业项目或开源项目,建议使用
MODULE_LICENSE("GPL v2"),以确保与内核完全兼容。 MODULE_AUTHOR应包含姓名和邮箱,方便社区或团队联系。MODULE_DESCRIPTION应简明扼要描述模块功能,如 "GPIO interrupt handler for sensor input"。MODULE_VERSION遵循语义化版本号,如 "1.0.0"。
3.5 完整的最小模块示例
将上述所有元素整合,得到一个完整的、可编译的内核模块源文件:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init gpio_driver_init(void)
{
printk(KERN_INFO "GPIO Driver: 初始化成功 (版本 %s)\n", DRV_VERSION);
return 0;
}
static void __exit gpio_driver_exit(void)
{
printk(KERN_INFO "GPIO Driver: 卸载完成\n");
}
module_init(gpio_driver_init);
module_exit(gpio_driver_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Android Kernel Team <kernel@android.com>");
MODULE_DESCRIPTION("GPIO Driver Module for Android Kernel Project");
MODULE_VERSION("1.0.0");
🧪 验证方法:
- 使用
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules编译生成.ko文件。 - 执行
insmod gpio_driver.ko加载模块,通过dmesg | tail查看打印信息。 - 执行
lsmod | grep gpio确认模块已加载。 - 执行
rmmod gpio_driver卸载模块,再次查看dmesg确认卸载信息。
3.6 本章小结
- 内核模块是 Linux 动态扩展功能的基石,在 Android 内核驱动开发中广泛使用。
module_init()和module_exit()是模块的入口和出口,必须正确实现。- 模块许可证声明(
MODULE_LICENSE)影响内核符号的访问权限,务必使用 GPL 兼容许可证。 - 作者、描述、版本等元信息有助于模块的管理和调试,是专业开发的基本素养。
- 掌握
insmod、rmmod、lsmod、modinfo等工具,是调试模块的基础技能。