4. GKI 版本迁移场景
GKI(Generic Kernel Image)迁移并不是单一动作,而是一组围绕内核接口、模块边界和启动链路的系统性改造。实际项目中,最常见的迁移需求可以归纳为三条主线:旧版 GKI 升级到新版 GKI、非 GKI 内核改造为 GKI、以及跨 Android 版本迁移。三者难度、风险点和验证重点各不相同,但都遵循“先解耦、再对齐、后验证”的基本逻辑。
一、核心原理:GKI 迁移的本质是接口契约迁移
GKI 的核心思想是把内核拆分为通用内核镜像(Generic Kernel Image)与供应商模块(Vendor Modules)两部分。通用部分由 Google 统一维护,供应商差异通过 KMI(Kernel Module Interface)和 GKI 模块机制承载。因此,迁移的本质不是“换一个内核版本”,而是把原来直接编译进内核的私有代码,迁移到符合 KMI 约束的模块或受控接口上。
KMI 由符号列表、结构体布局、函数签名和模块版本信息共同定义。只要 KMI 保持稳定,供应商模块就可以在不同 GKI 版本之间复用;一旦 KMI 发生破坏性变更,就必须同步修改模块并重新验证。理解这一点,就能明白为什么迁移工作往往集中在符号导出、结构体访问、模块加载顺序和启动参数上。
二、方法流程:三类场景的差异化操作
旧版 GKI 到新版 GKI通常是最“轻”的迁移。因为双方都遵循 GKI 框架,主要工作是核对 KMI 差异并重新编译供应商模块。典型命令如下:
# 查看当前内核 KMI 符号
adb shell cat /proc/kallsyms | grep -i "kmi"
# 检查模块版本与内核是否匹配
modinfo vendor_display.ko | grep vermagic
# 加载模块并观察日志
adb shell dmesg | grep -i "module_layout\|Unknown symbol"
如果出现 Unknown symbol 或 disagrees about version of symbol,说明 KMI 不匹配,需要更新模块或调整符号导出。
非 GKI 到 GKI是改造量最大的场景。原有代码往往把大量厂商驱动直接编译进 vmlinux,迁移时必须把这些代码拆分为独立模块,并只通过 KMI 允许的接口访问内核。常见动作包括:把 arch/arm64 下的板级代码迁移到设备树;把私有驱动改为 ko;把直接调用未导出符号改为使用 GKI 提供的稳定接口。
跨 Android 版本迁移则叠加了 Android 平台侧变化。除了内核本身,还要同步 vendor_boot、init_boot、SELinux 策略、HAL 接口和启动参数。例如 Android 13 到 Android 14 可能引入新的 bootconfig 字段或模块加载策略,必须逐项对齐。
三、典型场景与验证清单
下面这张清单可以作为迁移前的自检表,覆盖三类场景的共性检查点:
| 检查项 | 旧 GKI → 新 GKI | 非 GKI → GKI | 跨 Android 版本 |
|---|---|---|---|
| KMI 符号列表 | 必须逐项比对 | 需重新建立导出 | 随目标分支对齐 |
| 模块化改造 | 通常已完成 | 核心工作量 | 可能需二次拆分 |
| 设备树 / bootconfig | 小幅调整 | 需重构 | 需同步平台变更 |
| SELinux 策略 | 一般不变 | 需新增规则 | 重点适配 |
| 验证重点 | 模块加载与回归 | 启动与驱动功能 | CTS / VTS 全量 |
四、误区与复盘
常见误区一:只换内核版本,不改模块。很多团队认为升级 GKI 只是替换 Image,结果模块因 module_layout 校验失败而无法加载。正确做法是先确认 KMI 是否兼容,再决定模块是否需要重编。
常见误区二:把非 GKI 代码直接“塞进”模块。模块只能使用 KMI 导出的符号,直接引用未导出符号会导致编译或加载失败。必须通过 GKI 提供的稳定接口或 EXPORT_SYMBOL_GPL 白名单机制解决。
复盘建议:迁移完成后,保留一份 KMI 符号差异报告和模块加载日志。后续每次 GKI 升级都可以复用这份基线,快速判断哪些模块需要重新编译,哪些接口需要适配,从而把迁移从“一次性大工程”变成“可重复的例行操作”。
总体来看,三类迁移场景的差异在于改造深度,但共同的成功标准是一致的:KMI 对齐、模块可加载、系统可启动、功能可回归。抓住这条主线,就能在复杂的版本迁移中保持清晰的判断和可控的节奏。