2. Android内核基础
Android 的稳定性、功耗、安全与硬件适配能力,最终都落在 Linux 内核这一层。理解 Android 内核模块签名与加载,必须先建立三个基本认知:Linux 内核本身如何分层、Android 对内核做了哪些改造、以及“模块”与“驱动”之间到底是什么关系。本节围绕这三点展开,为后续签名机制与加载流程打下结构基础。
一、Linux 内核架构:分层与职责边界
Linux 内核并不是一团平铺的代码,而是按职责划分为若干子系统,并通过明确的接口相互调用。从下到上可以概括为:硬件层、体系结构相关层、核心子系统层、系统调用接口层,以及最上方的用户空间。
- 硬件层:CPU、内存、中断控制器、总线、外设控制器等物理资源。
- 体系结构层(arch):ARM64、x86 等平台相关代码,负责启动、异常向量、页表、缓存与平台初始化。
- 核心子系统:进程调度、内存管理、虚拟文件系统(VFS)、网络协议栈、设备模型、中断与时钟等。
- 系统调用接口:用户空间通过 syscall 进入内核,是应用与内核之间唯一的稳定边界。
其中“设备模型”是理解驱动与模块的关键:内核用 bus、device、driver 三类对象描述硬件与驱动的匹配关系。总线负责枚举与匹配,设备描述“有什么硬件”,驱动描述“如何操作这类硬件”,三者通过 probe 机制完成绑定。
二、Android 内核特点:在 Linux 之上做“加法”
Android 内核基于 Linux 长期支持版本,但并非原样使用。Google 通过 Android Common Kernel(ACK)维护一套公共补丁集,再叠加 SoC 厂商与设备厂商的定制。其主要特点包括:
| 特性方向 | 典型机制 | 作用 |
|---|---|---|
| 进程与内存 | Low Memory Killer、lmkd 协作 | 按优先级回收后台进程,保障前台体验 |
| 电源与唤醒 | Wakelocks、Suspend、Runtime PM | 控制休眠与唤醒,降低待机功耗 |
| 图形与显示 | Binder、ashmem、ION/DMA-BUF | 支撑跨进程通信与零拷贝图形缓冲 |
| 安全 | SELinux、dm-verity、模块签名 | 强制访问控制与系统完整性保护 |
| 调试与追踪 | ftrace、perf、KASAN | 性能分析与问题定位 |
这些改造使 Android 内核在保持 Linux 兼容性的同时,更贴近移动设备的功耗、内存与安全约束。也正因如此,Android 内核的模块加载策略比通用 Linux 更严格。
三、模块化机制:可加载内核模块(LKM)
Linux 支持将部分功能编译为可加载内核模块(Loadable Kernel Module,LKM),在运行时通过 insmod、modprobe 动态插入,通过 rmmod 卸载。模块以 .ko(kernel object)文件形式存在,本质是经过重定位的 ELF 目标文件。
# 查看已加载模块
lsmod
# 查看模块信息与依赖
modinfo gpu_drv.ko
# 加载与卸载
insmod /vendor/lib/modules/gpu_drv.ko
rmmod gpu_drv
# 查看内核日志
dmesg | tail -n 30
模块与内核之间通过符号表连接。内核导出符号(EXPORT_SYMBOL / EXPORT_SYMBOL_GPL),模块引用这些符号;加载时由内核完成符号解析与重定位。模块还必须声明许可证、作者、版本等信息,其中 MODULE_LICENSE 会影响其能否使用 GPL-only 符号。
四、内核模块与驱动的关系
“模块”是打包与加载形式,“驱动”是功能角色。二者是交叉关系而非等同关系:驱动可以编译进内核(built-in),也可以编译为模块;模块可以是驱动,也可以是文件系统、网络协议、安全模块等非驱动功能。
一个典型的字符设备驱动模块,其结构如下:
#include <linux/module.h>
#include <linux/fs.h>
static int demo_open(struct inode *i, struct file *f) { return 0; }
static const struct file_operations demo_fops = {
.owner = THIS_MODULE,
.open = demo_open,
};
static int __init demo_init(void)
{
pr_info("demo driver probed\n");
return 0;
}
static void __exit demo_exit(void)
{
pr_info("demo driver removed\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Android Kernel Course");
可以看到,模块通过 module_init / module_exit 注册入口与出口,通过 file_operations 向用户空间暴露接口。若该模块同时注册到平台总线,则还会包含 platform_driver 与 probe 回调,由设备树中的 compatible 属性触发匹配。
五、典型场景与常见误区
场景:某设备新增一颗传感器,厂商将其驱动编译为 sensor_x.ko,放在 /vendor/lib/modules/。系统启动时由 init 或 vendor 服务通过 modprobe 加载,内核完成符号解析后调用 probe,驱动注册到 IIO 子系统,HAL 即可通过 sysfs 读取数据。
常见误区二:认为
insmod 成功就代表驱动可用。加载成功只说明符号与版本匹配,能否工作还取决于设备树匹配、时钟/电源配置与 probe 结果。常见误区三:忽略内核版本与符号校验。模块与内核的 vermagic、符号 CRC 不匹配时会直接拒绝加载,这也是后续签名校验要解决的问题。
复盘来看,掌握本节的关键是建立一张“分层地图”:用户空间通过 syscall 进入核心子系统,核心子系统通过设备模型匹配驱动,驱动既可以内建也可以以模块形式动态加载,而模块的加载入口正是后续签名校验要守住的关口。理解这张地图,才能在遇到 insmod: invalid module format 或 Unknown symbol 时快速定位问题层级。