1. Linux内核模块基础
1.1 内核模块概念
Linux内核模块(Loadable Kernel Module,LKM)是一段可以在操作系统运行时动态加载到内核或从内核卸载的代码。它允许在不重新编译整个内核、不重启系统的情况下扩展内核功能。在Android驱动开发中,内核模块是连接硬件设备与上层框架的核心桥梁。
核心定义:内核模块是编译为独立二进制文件(.ko文件)的C程序,通过
insmod/modprobe命令动态插入内核地址空间,成为内核的一部分运行。
以下SVG展示了内核模块在Android系统软件栈中的位置:
1.2 模块与应用程序的区别
内核模块虽然也是C语言编写的程序,但其运行环境、资源访问方式、调试手段与用户态应用程序存在本质差异。理解这些区别是正确开发驱动的前提。
| 对比维度 | 内核模块(LKM) | 用户态应用程序 |
|---|---|---|
| 运行空间 | 内核空间(Ring 0),拥有最高特权级 | 用户空间(Ring 3),受MMU保护 |
| 入口函数 | module_init() / module_exit() |
main() |
| 内存访问 | 直接访问物理内存、I/O端口、内核数据结构 | 只能访问虚拟地址,通过系统调用请求内核服务 |
| 标准库支持 | 不能使用glibc,只能使用内核导出的API(printk替代printf) |
完整支持C标准库(glibc、musl等) |
| 浮点运算 | 默认禁止,需手动保存/恢复FPU状态 | 正常使用,由内核自动处理上下文切换 |
| 调试方式 | printk + dmesg、kgdb、JTAG |
gdb、strace、valgrind等丰富工具 |
| 崩溃影响 | 内核恐慌(Kernel Panic),系统崩溃 | 仅进程退出,系统不受影响 |
| 链接方式 | 动态插入内核符号表,与内核地址空间链接 | 动态链接器(ld.so)加载共享库 |
⚠ 关键警示:内核模块运行在特权级,一个空指针解引用或缓冲区溢出就会导致整个系统崩溃。在Android设备上,这意味着用户数据丢失、设备变砖。开发时必须格外谨慎,做好边界检查和错误处理。
以下SVG对比了用户态程序与内核模块的系统调用路径差异:
1.3 模块生命周期
每个内核模块从加载到卸载经历四个明确的阶段。Android驱动开发者必须理解每个阶段的行为,确保资源正确分配与释放。
生命周期四阶段
- 加载阶段(Loading):通过
insmod或modprobe将.ko文件读入内核内存,内核模块加载器解析ELF格式,重定位符号,调用module_init()函数。 - 运行阶段(Running):模块初始化成功后进入运行态,注册的设备驱动开始响应系统调用或硬件中断,模块代码常驻内核空间。
- 卸载阶段(Unloading):通过
rmmod触发,内核检查模块引用计数是否为0,调用module_exit()释放资源,从内核符号表移除。 - 消亡阶段(Dead):模块代码从内核地址空间完全移除,所有分配的内存、注册的设备号、proc条目等资源全部归还系统。
以下SVG完整展示了模块生命周期的状态转换流程:
1.4 模块引用计数与安全卸载
内核使用引用计数(reference count)机制防止模块在仍被使用时被卸载。每个模块维护一个refcnt字段,当有进程打开模块注册的设备、挂载模块提供的文件系统、或持有模块导出的符号时,引用计数递增。
💡 开发建议:在
module_exit()中必须释放所有注册的资源(设备号、proc条目、sysfs属性、内存等)。如果卸载时资源泄漏,轻则内核警告,重则导致后续模块加载失败或系统不稳定。使用rmmod -f强制卸载非常危险,仅用于调试。
模块加载与卸载的常用命令
| 命令 | 功能 | 依赖处理 | Android环境说明 |
|---|---|---|---|
insmod module.ko |
直接加载模块 | 不自动解决依赖 | Android root shell 常用 |
modprobe module |
智能加载模块 | 自动加载依赖模块 | 需要modules.dep文件 |
rmmod module |
卸载模块 | 检查引用计数 | 引用计数>0时返回EBUSY |
lsmod |
列出已加载模块 | 显示模块大小、引用计数、依赖 | Android中可用 |
modinfo module.ko |
查看模块信息 | 显示作者、描述、参数、许可证 | 验证模块元数据 |
1.5 最小内核模块代码示例
以下是一个完整的"Hello World"内核模块,展示了module_init和module_exit的基本结构:
#include <linux/module.h> // 所有模块都需要
#include <linux/kernel.h> // printk() 原型
#include <linux/init.h> // __init / __exit 宏
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, Android Kernel Module!\n");
printk(KERN_INFO "Module loaded successfully.\n");
return 0; // 返回0表示初始化成功
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, Android Kernel Module!\n");
printk(KERN_INFO "Module unloaded.\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Android Driver Developer");
MODULE_DESCRIPTION("A simple Hello World kernel module for Android");
MODULE_VERSION("1.0");
编译与测试步骤:
- 将上述代码保存为
hello.c,编写对应的Makefile(使用Android内核源码树)。 - 执行
make生成hello.ko。 - 在Android设备root shell中执行
insmod hello.ko。 - 使用
dmesg查看内核日志确认加载信息。 - 执行
rmmod hello卸载模块。