GKI改造原则、机制和方法

Google在android11-5.4分支上开始要求所有下游厂商使用Generic Kernel Image(GKI),需要将SoC和device相关的代码从核心内核剥离到可加载模块中(下文称之为GKI改造),从而解决内核碎片化问题。GKI为内核模块提供了稳定的内核模块接口(KMI),模块和内核可以独立更新。本文主要介绍了在GKI改造过程中需遵循的原则、遇到的问题和解决方法。

一、不能破坏KMI

冻结KMI后,分支在其整个生命周期中都保持冻结状态,原则上不会接受破坏KMI的修改(除非发现严重的安全问题,且不能在不影响KMI稳定的情况下得到缓解)。在冻结的分支中,只有不破坏KMI的bug修复和partner features才能被接收。在不影响现有KMI接口的前提下,可以使用新导出的符号扩展KMI,新接口被接收添加到KMI后,必须保持稳定,不能被将来的修改破坏。

1. 问题现象:

替换google boot.img后,开机串口有如下打印错误,

[    1.669135] init: Loading module /lib/modules/foo.ko with args ""

[    1.676281] foo: disagrees about version of symbol xxx

 

2. 原因分析:

出现这种错误可能的原因是ko中调用的符号与vmlinux中的符号crc值不匹配,如在KMI接口使用的结构体中增加了新字段,间接地修改了接口定义:

GKI改造原则、机制和方法_第1张图片

 

3. 推荐方法:

(1) 扩展内核原生结构体和接口

GKI改造原则、机制和方法_第2张图片

(2) 向google申请,在内核原生结构体中加入一些padding

Google在android11-5.4分支中新增了两个宏

 

ANDROID_VENDOR_DATA,在结构体中保留一些padding以备将来可能的使用,这些padding正常的都是位于结构体尾部,padding变量标识n从1开始递增。

ANDROID_VENDOR_DATA_ARRAY同ANDROID_VENDOR_DATA,分配一个数组,数组大小是s,元素是u64类型。

下面是使用ANDROID_VENDOR_DATA和ANDROID_VENDOR_DATA_ARRAY在内核结构体中增加新字段的示例:

GKI改造原则、机制和方法_第3张图片

4. 措施:

在编译脚本中增加检测,编译GKI kernel后,比较生成的Module.symvers和原生android/abi_gki_aarch64.xml文件中符号的crc值,如果crc值不匹配,编译报错,表示使用了非规则的方法修改原生接口或结构体。

 

二、内核模块只能使用已export并添加到白名单中的接口

android11-5.4分支build.config.gki.aarch64文件中有如下配置

GKI改造原则、机制和方法_第4张图片

表示模块只能使用abi_gki_aarch64文件中的符号。

1. 问题现象:

替换google boot.img后,串口有如下打印错误:

[    1.735506] foo: Unknown symbol xxx(err -2)

 

2. 原因分析:

原生内核没有用EXPORT_SYMBOL_GPL把接口xxx export,或已经export的接口没有添加到白名单(该接口abi可能不稳定)

 

3. 推荐方法:

(1)  Google建议向上游linux社区申请将要使用的内核接口export,并向Google申请,将接口添加到白名单android/abi_gki_aarch64_xxx

(2)使用其他已在白名单中的接口替代。

 

三、vendor hook机制

考虑到SoC和OEM厂商可能要对原生内核做一些客制化修改和优化,Google提供了一套vendor hook机制,下游厂商在需要修改内核源码的地方添加hook,并向Google申请,将patch upstream到AOSP。

1. vendor hook实现步骤如下:

(1) 在include/trace/hooks/目录下创建一个新的头文件xxx.h,定义一组hook接口register_trace_android_vh_xxx、trace_android_vh_xxx和全局变量__tracepoint_android_vh_xxx

GKI改造原则、机制和方法_第5张图片

(2) 在drivers/android/vendor_hooks.c文件中包含hook头文件xxx.h,export hook变量__tracepoint_android_vh_xxx给模块使用

(3)在内核模块中增加register代码将回调函数绑定到hook变量__tracepoint_android_vh_xxx

GKI改造原则、机制和方法_第6张图片

(4) 在内核xxx.c文件中包含hook头文件xxx.h,调用hook接口trace_android_vh_xxx(即hook变量__tracepoint_android_vh_xxx绑定的callback函数)

GKI改造原则、机制和方法_第7张图片

2. 问题现象:

测试偶现dump “BUG: scheduling while atomic:”

 

3. 原因分析:

vendor hook变量有两种,都是基于tracepoints的:

正常的:使用DECLARE_HOOK宏创建tracepoint函数trace_,要求name在trace中是独一无二的,callback函数的调用是在关抢占的场景中使用的

GKI改造原则、机制和方法_第8张图片

受限制的:受限制的hook在scheduler hook类的场景中使用,绑定的callback函数可以在cpu offline或非原子上下文中调用(调用前没有关抢占),受限制的vendor hook不能被解绑定,所以绑定的模块不能卸载,只允许有一个绑定(任何其他绑定将会返回-EBUSY错误)。

 

4. 推荐方法:

根据使用场景选择适合的vendor hook变量,在可能会调度的场景需要使用受限制的vendor hook

 

四、vendor hook延伸

SoC和OEM feature都要从内核剥离出来编译成内核模块,内核源码中相互调用export接口是没有问题的,那么模块之间相互调用export接口呢?

1. 问题现象:

编译报错 depmod: ERROR: Found 2 modules in dependency cycles!

 

2. 原因分析:

模块之间相互调用export接口,导致编译时报错。

GKI改造原则、机制和方法_第9张图片

3. 推荐方法:

借鉴Google的vendor hook机制,在A模块中定义并export全局变量,B模块初始化函数中将callback函数注册绑定到该全局变量,这样只有B模块调用A模块中的变量和接口,A模块通过hook变量回调B模块中接口,解决编译调用问题。

GKI改造原则、机制和方法_第10张图片

五、通过内核已有的事件注册接口替代vendor hook

是不是只能通过vendor hook实现修改内核呢?

android11-5.4分支log中有这么一笔关于vendor hook的提交:

为按键组合增加vendor hook

GKI改造原则、机制和方法_第11张图片

我们注意到内核中有一个接口input_register_handle,它的注释是注册一个新的input handle,添加到设备和handle列表中,只要使用input_open_device()接口打开设备,输入事件触发后会轮询到该handle,input_register_handle接口应该在handler的connect方法中调用,因此我们可以使用input_register_handler、input_register_handle来实现上述的按键组合功能,不需要在内核中增加vendor hook。

GKI改造原则、机制和方法_第12张图片

通过这个改造示例,启发我们思考是否只能使用vendor hook机制,是否可以使用其他内核已有的机制、事件注册接口将原先嵌入在内核中的feature剥离出来。

 

参考文章

[1]  https://source.android.com/devices/architecture/kernel/generic-kernel-image

[2]  https://blog.csdn.net/geshifei/article/details/94360470

[3]  https://blog.csdn.net/qq_39937242/article/details/82631165

GKI改造原则、机制和方法_第13张图片

长按关注

内核工匠微信

Linux 内核黑科技 | 技术文章 | 精选教程

你可能感兴趣的:(内核,android,javascript,python,js)