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 属性触发匹配。

Android 内核分层与模块/驱动关系 用户空间 User Space 应用 / Native 服务 / HAL · 通过 syscall 进入内核 系统调用接口 System Call Interface Linux 核心子系统 进程调度 内存管理 VFS / 网络栈 Binder / 电源 设备模型 bus / device / driver 安全与完整性 SELinux / dm-verity / 模块签名 可加载内核模块 LKM(.ko) 驱动模块 字符/块/网络/平台驱动 非驱动模块 文件系统/协议/安全 符号解析 · 重定位 · 签名校验 insmod / modprobe 触发 硬件与体系结构层 arch / SoC / 外设 CPU · 内存 · 中断控制器 · 总线 · GPU · 传感器

五、典型场景与常见误区

场景:某设备新增一颗传感器,厂商将其驱动编译为 sensor_x.ko,放在 /vendor/lib/modules/。系统启动时由 init 或 vendor 服务通过 modprobe 加载,内核完成符号解析后调用 probe,驱动注册到 IIO 子系统,HAL 即可通过 sysfs 读取数据。

常见误区一:把“模块”等同于“驱动”。实际上模块只是加载形式,非驱动功能同样可以做成模块。
常见误区二:认为 insmod 成功就代表驱动可用。加载成功只说明符号与版本匹配,能否工作还取决于设备树匹配、时钟/电源配置与 probe 结果。
常见误区三:忽略内核版本与符号校验。模块与内核的 vermagic、符号 CRC 不匹配时会直接拒绝加载,这也是后续签名校验要解决的问题。

复盘来看,掌握本节的关键是建立一张“分层地图”:用户空间通过 syscall 进入核心子系统,核心子系统通过设备模型匹配驱动,驱动既可以内建也可以以模块形式动态加载,而模块的加载入口正是后续签名校验要守住的关口。理解这张地图,才能在遇到 insmod: invalid module format 或 Unknown symbol 时快速定位问题层级。