第24章:内核与驱动兼容性:内核版本升级,GKI(通用内核镜像)对驱动的影响

说实话,做Android系统升级这么多年,内核和驱动的兼容性一直是最让我头疼的部分。每次Google发布新版本,我们这边就得加班加点适配驱动。直到GKI(通用内核镜像)出现,情况才真正有了转机。

这一章,我就跟你聊聊内核版本升级时,驱动到底会遇到哪些坑,以及GKI是怎么帮我们填坑的。

24.1 内核升级,驱动为什么容易崩?

先说说内核和驱动的关系。内核是操作系统的核心,驱动是内核和硬件之间的翻译官。内核升级,相当于翻译官换了套语法规则,驱动如果没跟上,硬件就罢工了。

我遇到过最典型的场景:手机从Android 12升级到Android 13,内核从4.19跳到5.10。结果Wi-Fi驱动直接挂掉,用户反馈说连不上网。查了半天,发现是内核里一个网络子系统的API变了,驱动还在用老接口。

为什么会这样?因为内核的内部API(比如函数名、数据结构、调用约定)经常变。驱动如果直接依赖这些内部API,升级内核就等于重写驱动。

核心问题:传统内核中,驱动和内核是紧耦合的。驱动直接调用内核内部函数,内核一改,驱动必崩。

24.2 GKI是什么?它怎么解决兼容性问题?

GKI的全称是通用内核镜像(Generic Kernel Image)。Google从Android 12开始强推这个东西。说白了,就是把内核分成两部分:

  • GKI内核:由Google维护,包含核心调度、内存管理、网络栈等。这部分是统一的,所有设备都用同一个镜像。
  • 供应商模块:由芯片厂商(高通、联发科等)维护,包含硬件驱动。这部分是独立的,通过标准接口和GKI通信。

我打个比方你就明白了。以前驱动是直接焊在内核主板上的,内核升级就得把整个主板换了。现在GKI相当于给驱动留了个标准插槽,驱动做成独立模块,插上去就能用。内核升级只换主板,驱动模块不用动。

我的经验:GKI最大的好处是,Google发布新内核版本,我们只需要验证供应商模块的接口是否兼容,不用重新编译所有驱动。以前升级一次内核要折腾两周,现在两天搞定。

24.3 GKI对驱动开发的具体影响

GKI引入了一个关键概念:稳定的内核模块接口(KMI,Kernel Module Interface)。驱动只能通过KMI和内核交互,不能直接调用内核内部函数。

这意味着驱动开发方式变了:

传统方式 GKI方式
驱动直接调用内核函数(如 kmalloc、printk) 驱动通过KMI调用,内核内部函数被封装
驱动可以访问内核数据结构(如 task_struct) 驱动只能访问KMI暴露的接口,内部结构被隐藏
内核升级后,驱动需要重新编译并适配 只要KMI不变,驱动无需修改,直接加载
驱动和内核版本强绑定 驱动和内核版本解耦,支持跨版本运行

举个例子,以前写一个简单的字符设备驱动,可能会这样:

// 传统方式:直接调用内核函数
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>

static int __init my_driver_init(void) {
    printk(KERN_INFO "My driver loaded\n");
    // 直接注册字符设备
    register_chrdev(0, "my_device", &fops);
    return 0;
}

在GKI环境下,驱动需要遵循KMI规范:

// GKI方式:通过KMI接口
#include <linux/module.h>
#include <linux/gki_module.h>  // GKI专用头文件

static int __init my_driver_init(void) {
    // 使用KMI提供的接口
    gki_printk(KERN_INFO "My driver loaded via GKI\n");
    // 通过KMI注册设备
    gki_register_device("my_device", &fops);
    return 0;
}

嗯,这里要注意:实际开发中,GKI的接口封装得更彻底。你几乎看不到直接操作内核数据结构的代码了。

24.4 避坑指南:GKI适配中的常见问题

我曾经踩过一个坑:某款手机的指纹驱动在GKI环境下死活加载不了。查了三天,发现是驱动里用了一个内核内部宏,这个宏在GKI版本中被移除了。

所以,我总结了几条经验:

  • 不要依赖内核内部头文件:比如 linux/sched.hlinux/mm.h 这些,GKI环境下可能被重构。只使用 linux/gki_module.h 和官方文档列出的接口。
  • 注意KMI版本号:每个GKI内核都有一个KMI版本(比如 android12-5.10-kmi)。驱动必须和KMI版本匹配,否则加载失败。
  • 测试跨版本兼容性:我建议在多个GKI内核版本上测试驱动。比如从5.10到5.15,虽然KMI理论上不变,但实际可能有细微差异。

警告:千万不要在GKI内核中直接修改内核源码。GKI内核由Google签名,修改后会导致设备无法通过验证启动(Verified Boot)。所有定制必须通过供应商模块实现。

24.5 GKI的架构图:驱动和内核如何解耦

下面这张图展示了GKI的核心架构。你可以看到,驱动不再直接和内核内部模块交互,而是通过KMI层进行通信。

GKI架构:驱动与内核解耦 用户空间(应用程序、框架层) 系统调用接口(Syscall Interface) GKI内核核心(通用部分) 进程调度 | 内存管理 | 网络栈 | 文件系统 | 安全模块 由Google维护,所有设备统一 KMI(内核模块接口)—— 稳定接口层 供应商模块(硬件驱动) Wi-Fi驱动 | 指纹驱动 | 显示驱动 | 传感器驱动

从图中你可以看到,驱动(供应商模块)只和KMI层打交道。内核核心升级时,只要KMI接口不变,驱动完全不用动。这就是GKI解决兼容性的核心思路。

24.6 实际项目中的GKI适配流程

我在做某款平板电脑的Android 13升级时,总结了一套GKI适配流程:

  1. 获取GKI内核:从Google的 android13-5.15 分支拉取GKI内核源码。
  2. 编译供应商模块:将原有驱动代码移植为供应商模块,确保只使用KMI接口。
  3. 验证KMI兼容性:使用Google提供的 kmi_check 工具,检查模块是否使用了非法接口。
  4. 集成测试:在GKI内核上加载供应商模块,测试所有硬件功能。
  5. 签名和发布:供应商模块需要和GKI内核一起签名,确保启动验证通过。

我的建议:如果你刚开始接触GKI,可以先从Google的官方文档 android-kernel-module-guide 入手。里面有个示例驱动,照着做一遍就明白了。

24.7 总结:GKI到底值不值得用?

说实话,GKI刚出来的时候,我也觉得麻烦。毕竟要改驱动代码,还要学新接口。但用下来,我觉得利远大于弊

以前内核升级,驱动适配是最大的瓶颈。现在有了GKI,驱动和内核解耦,升级效率提升明显。而且Google承诺,GKI内核会保持至少4年的KMI稳定性。这意味着你写一次驱动,能管好几年。

嗯,如果你还在用传统方式做内核开发,我建议你尽快切换到GKI。别等到Google强制要求了再改,那时候就手忙脚乱了。


公众号:蓝海资料掘金营,微信deep3321