第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.h、linux/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层进行通信。
从图中你可以看到,驱动(供应商模块)只和KMI层打交道。内核核心升级时,只要KMI接口不变,驱动完全不用动。这就是GKI解决兼容性的核心思路。
24.6 实际项目中的GKI适配流程
我在做某款平板电脑的Android 13升级时,总结了一套GKI适配流程:
- 获取GKI内核:从Google的
android13-5.15分支拉取GKI内核源码。 - 编译供应商模块:将原有驱动代码移植为供应商模块,确保只使用KMI接口。
- 验证KMI兼容性:使用Google提供的
kmi_check工具,检查模块是否使用了非法接口。 - 集成测试:在GKI内核上加载供应商模块,测试所有硬件功能。
- 签名和发布:供应商模块需要和GKI内核一起签名,确保启动验证通过。
我的建议:如果你刚开始接触GKI,可以先从Google的官方文档 android-kernel-module-guide 入手。里面有个示例驱动,照着做一遍就明白了。
24.7 总结:GKI到底值不值得用?
说实话,GKI刚出来的时候,我也觉得麻烦。毕竟要改驱动代码,还要学新接口。但用下来,我觉得利远大于弊。
以前内核升级,驱动适配是最大的瓶颈。现在有了GKI,驱动和内核解耦,升级效率提升明显。而且Google承诺,GKI内核会保持至少4年的KMI稳定性。这意味着你写一次驱动,能管好几年。
嗯,如果你还在用传统方式做内核开发,我建议你尽快切换到GKI。别等到Google强制要求了再改,那时候就手忙脚乱了。