2. GKI(通用内核映像)架构详解

2.1 GKI 架构演进背景

GKI(Generic Kernel Image)是 Google 为解决 Android 碎片化问题而推出的统一内核映像方案。其核心目标是将内核核心与设备驱动解耦,使 SoC 厂商和 OEM 厂商能够独立更新内核版本,而无需等待驱动适配。

GKI 1.0 随 Android 11 引入,GKI 2.0 随 Android 12 全面推行。两者在架构设计、模块化程度、ABI 稳定性方面存在显著差异,直接影响驱动开发者的移植策略。

核心认知: GKI 2.0 不是 GKI 1.0 的简单升级,而是从“内核统一”到“接口冻结”的质变。驱动开发者必须理解这一转变,否则移植工作将面临严重的兼容性问题。

2.2 GKI 1.0 架构特点与局限

GKI 1.0 主要解决了内核源码树统一的问题,但并未完全冻结内核接口。其架构特点如下:

  • 统一内核源码: 所有设备使用同一份 Android 通用内核源码(ACK),但允许 SoC 厂商在内核中修改核心代码。
  • 模块化驱动: 驱动以可加载内核模块(LKM)形式存在,但模块与内核之间的接口未做严格约束。
  • ABI 未冻结: 内核内部函数、数据结构、头文件允许修改,导致模块在不同内核版本间不兼容。
  • vendor_boot 分区: 引入 vendor_boot 分区存放设备树和 vendor 模块,但内核映像仍包含大量 vendor 定制代码。
GKI 1.0 痛点: 由于 ABI 未冻结,驱动模块必须与特定内核版本绑定。当 Google 发布安全补丁或内核升级时,SoC 厂商需要重新编译所有模块,导致 OTA 更新延迟数月。
GKI 1.0 架构示意图 GKI 1.0 内核核心 vendor 定制代码 驱动模块 A 驱动模块 B 驱动模块 C 内核接口未冻结 模块与内核强耦合 vendor 代码侵入内核 升级需全量重编

2.3 GKI 2.0 架构核心变化

GKI 2.0 引入了 稳定 ABI(KMI,Kernel Module Interface) 概念,将内核接口冻结为二进制兼容的契约。这是与 GKI 1.0 最本质的区别。

对比维度 GKI 1.0 GKI 2.0
ABI 稳定性 未冻结,接口可修改 KMI 冻结,接口二进制兼容
内核核心 允许 vendor 修改核心代码 核心代码只读,vendor 只能通过钩子扩展
驱动模块 与内核版本强绑定 跨内核版本兼容(同 KMI 基线)
vendor 定制 直接修改内核源码 通过 vendor_hooks 和 vendor_sysfs 扩展
更新策略 全量内核映像替换 仅更新 GKI 内核,模块无需重编
分区结构 boot + vendor_boot boot(GKI)+ vendor_boot(模块+DT)
KMI 冻结的含义: 在同一个 KMI 基线(如 android12-5.10)内,所有导出的内核符号、数据结构布局、函数签名必须保持向后兼容。驱动模块编译一次,即可在该基线的所有内核版本上运行。

2.4 GKI 2.0 架构分层

GKI 2.0 将系统分为三个层次,每层职责清晰,接口通过 KMI 契约连接:

  1. GKI 内核层: 包含核心调度、内存管理、文件系统、网络栈等通用功能。由 Google 维护,只读不可修改。
  2. KMI 接口层: 定义内核导出的符号集、数据结构、回调函数。通过 kmi_symbol_list 文件管理,任何修改需经 Google 审核。
  3. vendor 驱动层: 以可加载模块形式实现设备驱动、电源管理、传感器等 vendor 特有功能。只能使用 KMI 导出的接口。
GKI 2.0 分层架构与 KMI 接口 用户空间 (Android Framework / HAL) syscall GKI 内核层 (核心调度/内存/FS/网络) Google 维护,只读不可修改 KMI 接口层 (稳定 ABI 契约) kmi_symbol_list / vendor_hooks / sysfs vendor 驱动 A (LKM 模块) vendor 驱动 B (LKM 模块) vendor 驱动 C (LKM 模块) 驱动只能通过 KMI 接口访问内核,不能直接调用内核内部函数或修改内核数据结构

2.5 对驱动移植的具体影响

GKI 2.0 的架构变化对驱动移植工作产生了深远影响,主要体现在以下方面:

2.5.1 驱动接口使用限制

在 GKI 2.0 下,驱动只能使用 KMI 导出的符号。未导出的内核函数、内联函数、静态函数均不可直接调用。驱动开发者需要:

  • 通过 /proc/kallsyms 或 kmi_symbol_list 文件确认可用符号。
  • 使用 EXPORT_SYMBOL_GPL 导出的函数,避免使用 EXPORT_SYMBOL 未导出的内部接口。
  • 对于必须使用的未导出函数,需向 Google 提交 KMI 扩展请求。
// GKI 2.0 合规的驱动接口使用示例
#include <linux/module.h>
#include <linux/platform_device.h>

// 只能使用 KMI 导出的符号
extern int __kmi_exported_function(void);  // 在 kmi_symbol_list 中声明

static int my_driver_probe(struct platform_device *pdev)
{
    int ret;

    // 调用 KMI 导出的函数
    ret = __kmi_exported_function();
    if (ret < 0)
        return ret;

    // 错误做法:直接调用未导出的内核内部函数
    // ret = internal_kernel_function();  // 编译通过但运行时符号未解析

    return 0;
}

2.5.2 vendor_hooks 机制

GKI 2.0 引入 vendor_hooks 作为内核核心与 vendor 驱动之间的回调机制。驱动可以通过注册钩子函数,在内核关键路径上插入自定义逻辑,而无需修改内核代码。

钩子类型 触发时机 典型用途
trace_android_vh_xxx 调度、内存、文件系统等关键路径 性能优化、功耗管理、安全策略
trace_android_rvh_xxx 调度器相关路径 CPU 频率调节、任务优先级调整
trace_android_vh_xxx (网络) 网络协议栈路径 流量整形、防火墙规则
移植要点: 在 GKI 2.0 环境下,驱动移植的第一步是检查原有代码中是否包含对内核内部函数的直接调用。如果存在,必须改为通过 vendor_hooks 或 KMI 导出接口实现。否则,模块在 GKI 2.0 内核上将无法加载。

2.5.3 模块加载与版本校验

GKI 2.0 内核在加载模块时会进行严格的版本校验。模块的 vermagic 字符串必须与内核的 KMI 版本完全匹配,否则加载失败。

# 查看内核 KMI 版本
$ cat /proc/version
Linux version 5.10.107-android12-9-00001-g12345678abcde (build@host) ...

# 查看模块 vermagic
$ modinfo my_driver.ko
filename:       my_driver.ko
vermagic:       5.10.107-android12-9-00001-g12345678abcde SMP preempt mod_unload modversions aarch64

# 加载模块
$ insmod my_driver.ko
# 如果 vermagic 不匹配,会报错:
# insmod: ERROR: could not insert module my_driver.ko: Invalid module format

2.6 GKI 1.0 到 GKI 2.0 的移植路径

对于已有 GKI 1.0 驱动的团队,迁移到 GKI 2.0 需要遵循以下步骤:

  1. 审计驱动接口: 列出驱动使用的所有内核符号,对照 KMI 符号表,标记未导出符号。
  2. 替换内部调用: 将未导出符号的调用替换为 vendor_hooks 或 KMI 导出接口。
  3. 重构数据结构: 如果驱动直接访问内核结构体成员(如 task_struct 内部字段),需改为通过访问器函数操作。
  4. 更新 Makefile: 确保模块编译时链接正确的 KMI 头文件路径。
  5. 验证 KMI 兼容性: 使用 kmi_check 工具验证模块是否与目标 KMI 基线兼容。
GKI 1.0 → GKI 2.0 驱动移植流程 审计接口 列出所有符号 替换内部调用 vendor_hooks 重构数据结构 访问器函数 更新 Makefile KMI 头文件 KMI 验证 kmi_check 关键检查点:确保模块在 GKI 2.0 内核上加载后, 不产生任何符号未解析错误,且功能行为与 GKI 1.0 一致 验证失败 → 返回步骤 2

2.7 常见移植问题与解决方案

问题 原因 解决方案
模块加载报 Unknown symbol 驱动使用了未导出的内核符号 替换为 KMI 导出符号或 vendor_hooks
模块加载报 Invalid module format vermagic 与内核 KMI 版本不匹配 使用与目标内核相同的 KMI 基线编译
驱动功能异常但无报错 数据结构布局变化导致偏移错误 使用访问器函数替代直接结构体访问
vendor_hooks 未触发 钩子注册时机错误或钩子名称不匹配 检查内核版本对应的 vendor_hooks 列表
性能下降 vendor_hooks 引入额外上下文切换 优化钩子函数执行路径,减少锁竞争
重要提醒: GKI 2.0 的 KMI 冻结并非永久不变。Google 会在每个 Android 大版本升级时更新 KMI 基线(如 android12-5.10 → android13-5.15)。驱动开发者需要关注 KMI 基线的变更,并在新基线发布后重新验证模块兼容性。

2.8 小结

GKI 2.0 通过 KMI 冻结机制,实现了内核核心与 vendor 驱动的解耦,使驱动模块能够跨内核版本兼容。对于驱动移植工作,核心要点是:

  • 理解 KMI 契约的约束范围,避免使用未导出接口。
  • 熟练使用 vendor_hooks 机制替代内核代码修改。
  • 建立 KMI 兼容性验证流程,确保模块在不同内核版本间可加载运行。
  • 关注 KMI 基线的演进,及时适配新版本的变化。