赞
踩
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接口使用的结构体中增加了新字段,间接地修改了接口定义:
3. 推荐方法:
(1) 扩展内核原生结构体和接口
(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在内核结构体中增加新字段的示例:
4. 措施:
在编译脚本中增加检测,编译GKI kernel后,比较生成的Module.symvers和原生android/abi_gki_aarch64.xml文件中符号的crc值,如果crc值不匹配,编译报错,表示使用了非规则的方法修改原生接口或结构体。
二、内核模块只能使用已export并添加到白名单中的接口
android11-5.4分支build.config.gki.aarch64文件中有如下配置
表示模块只能使用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
(2) 在drivers/android/vendor_hooks.c文件中包含hook头文件xxx.h,export hook变量__tracepoint_android_vh_xxx给模块使用
(3)在内核模块中增加register代码将回调函数绑定到hook变量__tracepoint_android_vh_xxx
(4) 在内核xxx.c文件中包含hook头文件xxx.h,调用hook接口trace_android_vh_xxx(即hook变量__tracepoint_android_vh_xxx绑定的callback函数)
2. 问题现象:
测试偶现dump “BUG: scheduling while atomic:”
3. 原因分析:
vendor hook变量有两种,都是基于tracepoints的:
正常的:使用DECLARE_HOOK宏创建tracepoint函数trace_<name>,要求name在trace中是独一无二的,callback函数的调用是在关抢占的场景中使用的
受限制的:受限制的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接口,导致编译时报错。
3. 推荐方法:
借鉴Google的vendor hook机制,在A模块中定义并export全局变量,B模块初始化函数中将callback函数注册绑定到该全局变量,这样只有B模块调用A模块中的变量和接口,A模块通过hook变量回调B模块中接口,解决编译调用问题。
五、通过内核已有的事件注册接口替代vendor hook
是不是只能通过vendor hook实现修改内核呢?
android11-5.4分支log中有这么一笔关于vendor hook的提交:
为按键组合增加vendor hook
我们注意到内核中有一个接口input_register_handle,它的注释是注册一个新的input handle,添加到设备和handle列表中,只要使用input_open_device()接口打开设备,输入事件触发后会轮询到该handle,input_register_handle接口应该在handler的connect方法中调用,因此我们可以使用input_register_handler、input_register_handle来实现上述的按键组合功能,不需要在内核中增加vendor hook。
通过这个改造示例,启发我们思考是否只能使用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
长按关注
内核工匠微信
Linux 内核黑科技 | 技术文章 | 精选教程
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。