type
status
date
slug
summary
tags
category
icon
password
URL
一、名词解释
简称 | 全称 | 解释 |
ACK | Android Common Kernel | Android 通用内核 |
GKI | Generic Kernel Image | 通用内核镜像 |
QKI | Qcom GKI | 高通通用内核镜像 |
MGKI | MTK GKI | MTK通用内核镜像 |
ABI | Application Binary Interface | 应用二进制接口 |
KMI | Kernel Module Interface | 内核模块接口 |
二、引入GKI的背景
Android 通用内核 (ACK) 是所有 Android 产品内核的基础。供应商内核和设备内核位于 ACK 的下游。供应商通过修改内核源代码并添加设备驱动程序,添加了对 SoC 和外围设备的支持。这些修改内容可能很多,以至于设备上运行的代码中有多达 50% 是树外代码(并非来自上游 Linux 和 AOSP 通用内核)。
因此,设备内核由以下部分组成:
- 上游:来自 kernel.org 的 Linux 内核
- AOSP:AOSP 通用内核的其他 Android 专用补丁程序
- 供应商:供应商提供的 SoC 和外围设备支持以及优化补丁程序
- 原始设备制造商 (OEM)/设备:其他设备驱动程序和自定义项
几乎所有设备都具有自定义内核。这就导致了内核碎片化问题。

2.1 碎片化的代价
内核碎片化会对 Android 社区产生若干负面影响。
2.1.1 安全更新需要耗费大量人力
Android 安全公告 (ASB) 中引用的安全补丁程序必须向后移植到每个设备内核中。但是,由于存在内核碎片化问题,向正常使用的 Android 设备传播安全修复的代价非常之高。
2.1.2 很难合并长期支持的更新
长期支持 (LTS) 版本包含安全修复和其他重大问题修复。事实证明,使用最新的 LTS 版本是提供安全修复的最有效方式。我们发现,ASB 报告的内核安全问题中有 90% 都已在保持最新状态的 Pixel 设备上得到修复。
不过,由于设备内核中所有的自定义修改,很难仅将 LTS 修复合并到设备内核中。
2.1.3 妨碍 Android 平台进行版本升级
由于碎片化问题,很难向正常使用的设备添加需要更改内核的 Android 新功能。Android 框架代码必须假设支持的内核版本多达 5 个,并且没有针对新的平台版本进行任何内核更改(Android 10 支持内核版本 3.18、4.4、4.9、4.14 和 4.19;在某些情况下,这些版本自 2017 年 Android 8 发布以来还未添加新功能)。
2.1.4 很难将内核更改贡献回上游 Linux
对内核进行完所有更改后,大多数旗舰设备附带的内核版本已经至少存在 18 个月了。例如,
kernel.org
于 2017 年 11 月发布了 4.14 版内核,而首批使用 4.14 版内核的 Android 手机于 2019 年春季才发布。上游内核发布与产品发布之间的这种长时间延迟导致 Android 社区很难将所需的功能和驱动程序馈送到上游内核中,因此解决碎片化问题并非易事。
2.2 引入GKI
通用内核映像 (GKI) 项目通过统一核心内核并将 SoC 和板级支持从核心内核移至可加载模块中,解决了内核碎片化问题。GKI 内核为内核模块提供了稳定的内核模块接口 (KMI),因此模块和内核可以独立进行更新。
GKI 具有以下特点:
- 基于 ACK 来源构建而成。
- 是每个架构和每个 LTS 版本的单内核二进制文件以及关联的可加载模块(目前只有适用于
android11-5.4
和android12-5.4
的 arm64)。
- 已经过关联 ACK 支持的所有 Android 平台版本的测试。在 GKI 内核版本的生命周期内不会发生功能弃用。
- 为给定 LTS 中的驱动程序提供了稳定版 KMI。
- 不包含 SoC 专用代码或板卡专用代码。
下图即为实现了 GKI 的 Android 设备。

2.2.1 GKI 1.0
对于 Android 11 平台版本,为了保证与 Treble 兼容,必须对运行 v5.4 内核的设备进行 GKI 测试

通过刷入GKI到boot分区+刷GSI到system分区来安装通用系统镜像和GKI内核,来通过VTS和CTS-on-GSI+GKI测试。产品内核和 GKI 内核都必须从相同的
vendor_boot
和 vendor
分区加载模块。因此,所有产品内核都必须具有相同的二进制内核模块接口 (KMI)。GKI 1.0 的目标
- 当产品内核被 GKI 内核取代时,不在 VTS 或 CTS 中引入性能降低问题。
- 减少 OEM 和供应商为了使 AOSP 通用内核保持最新状态而进行的内核维护工作。
- 无论是升级到新 Android 平台版本的设备还是新发布的设备,都在内核中加入核心 Android 变更。
- 绝不破坏 Android 用户空间。
- 将硬件专用组件作为可加载模块从核心内核中分离出来。
2.2.2 GKI 2.0
搭载 Android S (2021) 平台版本且使用内核版本 v5.x(5.x 是 2020 年年底被选为 LTS 的内核版本)或更高版本的设备必须附带 GKI 内核。将提供已签名的启动映像,并通过 LTS 和重大问题修复定期对其进行更新。由于 KMI 将保持二进制稳定性,因此无需对供应商映像进行任何更改,即可安装这些启动映像。
GKI 2.0 的目标
- 不为 GKI 引入明显的性能或能效降低问题。
- 使 OEM 无需供应商参与即可提供内核安全修复和问题修复 (LTS)。
- 降低更新设备主要内核版本(例如,将 v5.x 更新为 v5.y)所需的费用。
- 通过按照清晰的升级过程更新内核版本,只为每个架构维护一个 GKI 内核二进制文件。
三、KMI
GKI具有内核版本的唯标识符,内核版本由内核模块接口(KMI)和子级别组成。内核版本特定于要发布的映像,而 KMI 版本代表构建版本的接口。一个 KMI 版本可以支持多个内核版本。内核版本仅与一个 KMI 版本相关联。
ABI 监控工具(kernel_platform/build/kernel/abi/)可在预提交测试期间监控 KMI 稳定性。
如果以不兼容的方式修改了现有 KMI 符号,相应 KMI 会被视为已遭到破坏。示例:
- 向 KMI 函数添加了新参数
- 向 KMI 函数所用的结构添加了新字段
- 添加了新的枚举值,进而更改了 KMI 函数所用的枚举的值
- 更改了配置,进而更改了影响 KMI 的数据成员的存在状态
Android 内核 ABI 监控:
https://source.android.com/devices/architecture/kernel/abi-monitor
3.1 KMI 兼容问题案例
编译GKI kernel后,比较生成的Module.symvers和原生android/abi_gki_aarch64.xml文件中符号的crc值,如果crc值不匹配,编译报错,表示使用了非规则的方法修改原生接口或结构体。
kernel/prebuilts/5.15/arm64/: 规定了ABI接口,GKI方法的符号
kernel_platform/common/android/:google文件符号CRC值,已经各个vendor厂商的白名单
- depmod: ERROR: Found 12 modules in dependency cycles!
- "Error: xxx symbol undefined"
3.2 详细实例参考:
https://blog.csdn.net/feelabclihu/article/details/113409593
KBA-210114010438 的 About Build failures:章节
GKI2.0 要求vendor使用google的通用boot.img, 那如果vendor想要在boot.img中添加自己的代码怎么办? 除了将code upstream 给社区之外,google还提供一个 vendor hook的机制。
如果确定要用vendor hook这个机制来实现GKI2.0,那需要在锁定期前,将hook接口提交给google。
Xiaomi WIKI:https://wiki.n.miui.com/display/~wangting11/2021/01/23/GKI2.0+-+vendor+hook
四、ABI check
五、模块加载准则
5.1 正确使用init/exit函数
驱动程序模块必须在
module_init()
中注册驱动程序,并在 module_exit()
中取消注册驱动程序。强制执行这些限制的一种简单方法是使用封装容器宏,这样可避免直接使用 module_init()
、*_initcall()
或 module_exit()
宏。- 对于可卸载的模块,请使用
module_subsystem_driver()
。示例:module_platform_driver()
、module_i2c_driver()
和module_pci_driver()
。
- 对于无法卸载的模块,请使用
builtin_subsystem_driver()
。示例:builtin_platform_driver()
、builtin_i2c_driver()
和builtin_pci_driver()
。
注意:您可以使用
builtin_subsystem_driver()
作为模块来构建驱动程序,但这类驱动程序一经成功加载便无法卸载。某些驱动程序模块会使用
module_init()
和 module_exit()
,因为它们要注册多个驱动程序。对于使用 module_init()
和 module_exit()
注册多个驱动程序的驱动程序模块,请尽量将这些驱动程序组合到单个驱动程序中。例如,您可以通过使用 compatible
字符串或设备的 Aux 数据进行区分,而不是分别注册单独的驱动程序。 或者,您也可以将驱动程序模块拆分为两个模块。5.2 避免由于前向声明的数据类型而导致 CRC 不一致问题
请勿通过添加头文件获知前向声明的数据类型。头文件 (
header-A.h
) 中定义的一些结构体、并集和其他数据类型可以在另一个头文件 (header-B.h
) 中前向声明,而后者通常使用指向这些数据类型的指针。此代码模式意味着内核会故意使数据结构对 header-B.h
的使用者保持不公开状态。header-B.h
的使用者不应通过添加 header-A.h
直接访问这类前向声明的数据结构的内部信息。否则,当其他内核(如 GKI 内核)尝试加载该模块时,会出现 CONFIG_MODVERSIONS
CRC 不一致问题(从而导致 ABI 合规性问题)。例如,
struct fwnode_handle
需在 include/linux/fwnode.h
中定义,但在 include/linux/device.h
中前向声明为 struct fwnode_handle;
,因为内核会试图使 struct fwnode_handle
的详细信息对 include/linux/device.h
的使用者保持不公开状态。在这种情况下,请不要通过在模块中添加 #include <linux/fwnode.h>
来获取对 struct fwnode_handle
成员的访问权限。对于任何设计而言,如果您不得不添加此类头文件,便表明该设计的模式不合理。5.3 请勿直接访问核心内核结构
直接访问或修改核心内核数据结构可能会导致出现不良行为,包括内存泄漏、崩溃以及与后续内核版本的兼容性中断。数据结构只要满足以下任一条件即为核心内核数据结构:
- 数据结构在
KERNEL-DIR/include/
下定义。例如,struct device
和struct dev_links_info
。在include/linux/soc
中定义的数据结构除外。
- 数据结构由模块进行分配或初始化,但通过作为内核导出的函数中的输入来间接传递(通过结构体中的指针)或直接传递的方式,对内核可见。例如,
cpufreq
驱动程序模块会初始化struct cpufreq_driver
,然后将其作为输入传递给cpufreq_register_driver()
。在此之后,cpufreq
驱动程序模块不应直接修改struct cpufreq_driver
,因为调用cpufreq_register_driver()
会使struct cpufreq_driver
对内核可见。
- 数据结构不会由模块初始化。例如,
regulator_register()
返回的struct regulator_dev
。
仅通过内核导出的函数或通过明确作为输入传递给供应商钩子的参数访问核心内核数据结构。如果您没有用于修改核心内核数据结构组成部分的 API 或供应商钩子,那么这可能是有意行为,您不应通过模块修改数据结构。例如,请勿修改
struct device
或 struct device.links
中的任何字段。- 如需修改
device.devres_head
,请使用devm_*()
函数,例如devm_clk_get()
、devm_regulator_get()
或devm_kzalloc()
。
- 如需修改
struct device.links
中的字段,请使用device_link_add()
或device_link_del()
等设备链接 API。
5.4 请勿解析具有兼容属性的设备树节点
如果某个设备树 (DT) 节点具有
compatible
属性,系统会自动为其分配 struct device
或者当 of_platform_populate()
在父 DT 节点上被调用(通常由父设备的设备驱动程序调用)时会分配。默认预期(为调度程序提前初始化的某些设备除外)是具有 compatible
属性的 DT 节点会具有 struct device
和匹配的设备驱动程序。所有其他异常情况均已由上游代码处理。此外,
fw_devlink
(以前称为 of_devlink
)将具有 compatible
属性的 DT 节点视为分配有 struct device
(由驱动程序探测)的设备。如果某个 DT 节点具有 compatible
属性,但驱动程序未探测到分配的 struct device
,则 fw_devlink
可能会阻止其使用方设备进行探测,也可能会阻止为其提供方设备调用 sync_state()
。如果您的驱动程序使用
of_find_*()
函数(例如 of_find_node_by_name()
或 of_find_compatible_node()
)直接查找具有 compatible
属性的 DT 节点,然后解析该 DT 节点,您可以通过编写可探测设备或移除 compatible
属性的设备驱动程序来修正该模块(只有在尚未向上游传送该模块的情况下才有可能解决问题)。如需讨论替代方案,请通过发送电子邮件至 kernel-team@android.com 来联系 Android 内核团队;您将需要为自己的用例给出合适的理由。注意:对于 GKI,必须使用
fw_devlink
。5.5 请勿从探测函数内加载固件
驱动程序不应从
.probe()
函数内加载固件,因为如果驱动程序在闪存或基于永久性存储空间的文件系统安装之前进行探测,则可能会无法访问固件。在这种情况下,request_firmware*()
API 可能会长时间阻塞,然后失败,而这可能会导致启动过程发生不必要的减慢。正确的做法是将固件加载延迟到客户端开始使用设备时。例如,显示驱动程序可在显示设备打开时加载固件。在某些情况下,您可以使用
.probe()
加载固件,例如在需要固件才能正常运行但设备未暴露到用户空间的时钟驱动程序中。可能还有其他合适的用例。5.6 使用 #if IS_ENABLED()(而非 #ifdef)进行配置
使用
#if IS_ENABLED(CONFIG_XXX)
(而非 #ifdef CONFIG_XXX
)可确保 #if
代码块中的代码在日后配置变为三态配置后仍能编译。区别如下:- 当
CONFIG_XXX
设置为模块 (=m
) 或内置项 (=y
) 时,#if IS_ENABLED(CONFIG_XXX)
的求值结果为true
。
- 当
CONFIG_XXX
设置为内置项 (=y
) 时,#ifdef CONFIG_XXX
的求值结果为true
,但当CONFIG_XXX
设置为模块 (=m
)时,则不会得出该求值结果。只有当您确定要在这项配置设置为模块或停用时执行相同的操作,才应使用此项设置。
注意:即使配置是布尔值,也请使用
#if IS_ENABLED(CONFIG_XXX)
。5.7 使用正确的宏实现按条件编译
警告:这可能会使头文件变得棘手!请仔细阅读以下详细说明。
如果
CONFIG_XXX
设置为模块 (=m
),构建系统会自动定义 CONFIG_XXX_MODULE
。如果您的驱动程序由 CONFIG_XXX
控制,并且您希望检查驱动程序是否会被编译为模块,请遵循以下准则:- 在驱动程序的 C 文件(或任何不是头文件的源文件)中,请勿使用
#ifdef CONFIG_XXX_MODULE
,因为它会造成不必要的限制,而且在配置重命名为CONFIG_XYZ
的情况下还会失效。对于编译到模块中的任何非头文件性质的源文件,构建系统会自动为该文件的作用域定义MODULE
。因此,如要检查 C 文件(或任何非头文件性质的源文件)是否会被编译到模块中,请使用#ifdef MODULE
(不带CONFIG_
前缀)。
- 在头文件中,要执行相同的检查会更为棘手,因为头文件不直接编译到二进制文件中,而是编译到 C 文件(或其他源文件)中。对于头文件,请遵循以下规则:
- 对于使用
#ifdef MODULE
的头文件,结果会根据使用它的源文件而变化。这意味着,对于同一 build 中的同一头文件,系统会针对不同的源文件(模块/内置项或停用)编译头文件内不同部分的代码。如果您想定义一个宏,让其针对内置代码以一种方式扩展,而针对模块以另一种方式扩展,那么这会很有用。 - 如果某个头文件在特定
CONFIG_XXX
设为模块(无论其所在的源文件是否为模块)时需要在代码段中编译,则该头文件必须使用#ifdef CONFIG_XXX_MODULE
。
六、内核模块加载
6.1 文件位置
启动模式 | 存储 | 显示 | 拨号键盘 | 电池 | PMIC | 触摸屏 | NFC/WLAN/蓝牙 | 传感器 | 相机 |
恢复 | Y | Y | Y | Y | Y | N | N | N | N |
充电 | Y | Y | Y | Y | Y | N | N | N | N |
Android | Y | Y | Y | Y | Y | Y | Y | Y | Y |
如果使用了内核模块,它们在文件系统中的放置位置的要求如下:
- 所有内核都应内置对启动和装载分区的支持。
- 必须从只读分区加载内核模块。
- 对于需要支持启动时验证的设备,应从已验证的分区加载内核模块。
- 内核模块不应位于
/system
中。
- 完整 Android 模式或充电模式所需的 SoC 供应商内核模块应位于
/vendor/lib/modules
中。
- 如果存在 ODM 分区,完整 Android 模式或充电模式所需的 ODM 内核模块应位于
/odm/lib/modules
中。如果不存在,这些模块应位于/vendor/lib/modules
中。
- 恢复模式所需的 SoC 供应商内核模块和 ODM 内核模块应位于恢复
ramfs
的/lib/modules
中。
- 恢复模式和完整 Android/充电模式都需要的内核模块应同时位于恢复
rootfs
以及/vendor
或/odm
分区中(如上所述)。
- 恢复模式所用的内核模块不应依赖仅位于
/vendor
或/odm
中的模块,因为这些分区在恢复模式下没有装载。
- SoC 供应商内核模块不应依赖 ODM 内核模块。
6.2 Android构建系统支持
在
BoardConfig.mk
中,Android 构建系统定义了 BOARD_VENDOR_KERNEL_MODULES
变量,此变量提供了用于供应商映像的内核模块的完整列表。此变量中列出的模块会被复制到供应商映像的 /lib/modules/
中,在 Android 中装载后会出现在 /vendor/lib/modules
中(根据上述要求)。下面是一个供应商内核模块的配置示例:
恢复映像可能包含一部分供应商模块。Android 构建系统为这些模块定义了
BOARD_RECOVERY_KERNEL_MODULES
变量。示例:Android 构建系统负责运行
depmod
,以在 /vendor/lib/modules
和 /lib/modules
(recovery ramfs
) 中生成所需的 modules.dep
文件。6.3 模块加载和版本控制
您可以通过调用
modprobe -a
从 init.rc*
一次加载所有内核模块。这样可以避免重复初始化 modprobe
二进制文件的 C 运行时环境所产生的开销。您可以修改 early-init
事件以调用 modprobe
:6.4 模块位置
ramdisk 是第一阶段
init,
的文件系统,也是 A/B 设备和虚拟 A/B 设备上的恢复/fastboot 映像的文件系统。这是一个 initramfs
。- 第一阶段
init
供应商内核模块,位于/lib/modules/
。
modprobe
配置文件,位于/lib/modules/
:modules.dep
、modules.softdep
、modules.alias
、modules.options
。
- 一个
modules.load
文件,用于指示在第一阶段 init 期间加载的模块及相应的加载顺序,位于/lib/modules/
。
- 供应商恢复内核模块,用于 A/B 和虚拟 A/B 设备,位于
/lib/modules/
。
modules.load.recovery
,用于指示要加载的模块及相应的加载顺序,用于 A/B 和虚拟 A/B 设备,位于/lib/modules
。
6.5 启动阶段
第一阶段
init
首先从 ramdisk 上的 /lib/modules/
读取 modprobe 配置文件。接下来,读取 /lib/modules/modules.load
(在恢复过程中,则为 /lib/modules/modules.load.recovery
)中指定的模块列表,并尝试按照之前加载的文件中指定的配置,依次加载各个模块。为了满足硬依赖项或软依赖项的要求,实际执行顺序可以不同于请求的顺序。6.5.1 build 支持,第一阶段 init
如需指定要复制到供应商 ramdisk cpio 的内核模块,请在
BOARD_VENDOR_RAMDISK_KERNEL_MODULES
中将其列出。build 会对这些模块运行 depmod
,并将生成的 modprobe 配置文件放在供应商 ramdisk cpio 中。build 还会创建一个
modules.load
文件,并将其存储在供应商 ramdisk cpio 中。默认情况下,该文件包含 BOARD_VENDOR_RAMDISK_KERNEL_MODULES
中列出的所有模块。如需替换该文件的内容,请使用 BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD
,如以下示例所示:6.5.2 第二阶段 init、vendor 或 vendor_dlkm 分区
由于第一阶段 init 过程是序列化的,因此并行化启动过程的机会并不多。如果一个模块在完成第一阶段 init 时用不到,请将模块放入 vendor 或
vendor_dlkm
分区,从而将其移至第二阶段 initBOARD_VENDOR_KERNEL_MODULES_LOAD
:要在第二阶段 init 中加载的模块列表6.5.3 build 支持,所有 Android 版本
与在 Android 10 及更低版本中一样,
BOARD_VENDOR_KERNEL_MODULES
中列出的内核模块会由 Android 平台 build 复制到 vendor 分区中的 /vendor/lib/modules
。平台 build 会对这些模块运行 depmod
,并将 depmod
输出文件复制到 vendor 分区中的相同位置。从 /vendor
加载内核模块的机制与以前的 Android 版本相同。您可以决定如何以及何时加载这些模块,尽管这通常是使用 init.rc
脚本来完成的。6.5.4 通配符和集成的内核 build
如果供应商将设备内核 build 与 Android 平台 build 相结合,可能就会无法使用上述
BOARD
宏来指定要复制到设备的内核模块。如果供应商不希望在设备的平台 build 文件中列出内核模块,可以使用通配符 ($(wildcard device/vendor/mydevice/*.ko
)。请注意,如果集成了内核 build,该通配符会不起作用,因为调用 make 并在 makefile 中展开宏时,内核模块尚未构建,因此宏为空。为了解决此问题,供应商可以让其内核 build 创建一个 zip 归档文件,其中包含要复制到每个分区的内核模块。将该 zip 归档文件的路径设置到
BOARD_*_KERNEL_MODULES_ARCHIVE
,其中 *
为分区的名称(例如 BOARD_VENDOR_KERNEL_MODULES_ARCHIVE
)。Android 平台 build 会将此 zip 归档文件解压缩到适当的位置,并对模块运行 depmod
。内核模块 zip 归档文件应包含一条 Make 规则,用于确保平台 build 可以在需要时生成归档文件。
6.6 recovery模式
在之前的 Android 版本中,恢复所需的内核模块在
BOARD_RECOVERY_KERNEL_MODULES
中指定。在 Android 11 中,恢复所需的内核模块仍使用该宏指定。但是,恢复内核模块会被复制到供应商 ramdisk cpio,而不是通用的 ramdisk cpio。默认情况下,BOARD_RECOVERY_KERNEL_MODULES
中列出的所有内核模块都会在第一阶段 init
期间加载。如果您只想加载这些模块中的一部分,请在 BOARD_RECOVERY_KERNEL_MODULES_LOAD
中指定这一部分的内容。6.7 模块加载逻辑
宏 | 解释 |
BOARD_VENDOR_RAMDISK_KERNEL_MODULES | 要复制到 ramdisk 的模块列表 |
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD | 要在第一阶段 init 中加载的模块列表 |
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD | 从 ramdisk 中选择 recovery 或 fastbootd 时要加载的模块列表 |
BOARD_VENDOR_KERNEL_MODULES | 要复制到 vendor 或 vendor_dlkm 分区中的 /vendor/lib/modules/ 目录下的模块列表 |
BOARD_VENDOR_KERNEL_MODULES_LOAD | 要在第二阶段 init 中加载的模块列表 |

六、启动时间优化
6.1 从模块中剥离调试符号
请确保从模块中剥离调试符号,方法与在正式版设备上从内核中剥离调试符号类似。从模块中剥离调试符号有助于减少以下过程耗用的时间,从而缩短启动时间:
- 从闪存中读取二进制文件。
- 解压缩 ramdisk。
- 加载模块。
从模块中剥离调试符号可在启动过程中节省几秒钟时间。
在 Android 平台 build 中,符号剥离默认处于启用状态,但若要明确启用此功能,则需要在 device/vendor/device 下的设备专用 config 中设置
BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES
。6.2 对内核和 ramdisk 使用 LZ4 压缩
Gzip 生成的压缩输出比 LZ4 小,但 LZ4 的解压缩速度比 Gzip 快。对于内核和模块而言,使用 Gzip 减少的绝对存储大小相比使用 LZ4 节省的解压缩时间而言,并没有明显的优势。
Android 平台 build 已通过
BOARD_RAMDISK_USE_LZ4
添加了对 LZ4 ramdisk 压缩的支持。您可以在设备专用 config 中设置此选项。内核压缩可通过内核 defconfig 设置。切换到 LZ4 会使启动时间快 500 到 1000 毫秒。
6.3 避免在驱动程序中进行过多的日志记录
减少日志记录可以节省约 100 - 300 毫秒的启动时间,具体取决于现有日志记录的过度程度。
6.4 选择性地启用异步探测
加载模块时,如果已从 DT(设备树)填充了模块所支持的设备并将其添加到了驱动程序核心中,则会在
module_init()
调用的上下文中完成设备探测。在 module_init()
的上下文中完成设备探测时,模块加载要等到探测完成后才能完成。由于模块加载在很大程度上是序列化的,因此探测时间相对较长的设备会使启动变慢。为避免启动变慢,可以为需要花费一定时间来探测其设备的模块启用异步探测。为所有模块都启用异步探测可能没有好处,因为创建线程分支并启动探测所需的时间可能与探测设备所需的时间一样长。
通过慢总线(例如 I2C)连接的设备、在其探测功能中执行固件加载的设备,以及执行大量硬件初始化的设备都可能导致启动变慢。识别这种情况发生的最好办法就是收集每个驱动程序的探测时间并对其进行排序。
若要为模块启用异步探测,仅在驱动程序代码中设置
PROBE_PREFER_ASYNCHRONOUS
标志是不够的。对于模块,您还需要在内核命令行中添加 module_name.async_probe=1
,或者在使用 modprobe
或 insmod
加载模块时将 async_probe=1
作为模块参数传递。注意:如果驱动没有启动顺序的关联性,可以使用异步探测
启用异步探测可以节省约 100 - 500 毫秒的启动时间,具体取决于您的硬件/驱动程序。
6.5 将模块移至第二阶段 init、vendor 或 vendor_dlkm 分区
由于第一阶段 init 过程是序列化的,因此并行化启动过程的机会并不多。如果一个模块在完成第一阶段 init 时用不到,请将模块放入 vendor 或
vendor_dlkm
分区,从而将其移至第二阶段 init。第一阶段 init 不需要探测多个设备才能进入第二阶段 init。正常启动流程只需要控制台和闪存功能。
加载以下基本驱动程序:
- watchdog
- reset
- cpufreq
对于恢复模式 (Recovery mode) 和用户空间
fastbootd
模式,第一阶段 init 需要探测和显示更多设备(例如 USB)。请在第一阶段 ramdisk 和 vendor 或 vendor_dlkm
分区中保存这些模块的副本。这样一来,便可在第一阶段 init 中加载这些模块来执行恢复或 fastbootd
启动流程。不过,在正常启动流程中,请勿在第一阶段 init 中加载恢复模式 (Recovery mode) 模块。恢复模式 (Recovery mode) 模块可以推迟到第二阶段 init 进行加载,以缩短启动时间。第一阶段 init 中不需要的所有其他模块都应移至 vendor 或 vendor_dlkm
分区。给定一个叶设备(例如 UFS 或串行设备)列表,
dev needs.sh
脚本会查找依赖项或提供方(例如,时钟、调节器或 gpio
)需要探测的所有驱动程序、设备和模块。将模块移至第二阶段 init 可通过以下方式缩短启动时间:
- 缩减 Ramdisk 大小。
- 这在引导加载程序加载 ramdisk(序列化启动步骤)时可以加快闪存读取速度。
- 这在内核解压缩 ramdisk(序列化启动步骤)时可以加快解压缩速度。
- 第二阶段 init 是并行运行的,模块加载与第二阶段 init 中的工作同步进行,因而可以省去单独加载模块的时间。
将模块移至第二阶段可以节省 500 - 1000 毫秒的启动时间,具体取决于可以将多少个模块移至第二阶段 init。
对于第二阶段 init,建议以服务的形式运行模块加载,以免它阻止启动流程。请使用 Shell 脚本来管理模块加载,以便在必要时可以报告(或忽略)其他逻辑,如错误处理和缓解,或模块加载完成。
可以忽略用户 build 中不存在的调试模块加载失败情况。如需忽略此失败情况,请设置
vendor.device.modules.ready
属性,使其触发 init rc
脚本启动流程的后续阶段,以继续执行到启动屏幕在模块从第一阶段移至第二阶段后,可以进行其他优化。可以使用 modprobe blocklist 功能来拆分第二阶段启动流程,以纳入非必要模块的延迟模块加载。可以将特定 HAL 专用模块的加载推迟到相应 HAL 启动时才进行。
为了让用户明显感觉到启动时间的缩短,可以在模块加载服务中专门选择更适合在启动屏幕之后加载的模块。例如,可以将视频解码器模块或 Wi-Fi 模块明确推迟到 init 启动流程结束(例如,Android 属性信号
sys.boot_complete
)之后再加载。请确保延迟加载的模块所对应的 HAL 在内核驱动程序不存在的情况下阻塞足够长的时间。6.6 驱动程序不应在第一阶段 init 中加载固件
可能会有一些不可避免的情况,必须要在第一阶段 init 中加载固件。但一般而言,驱动程序不应在第一阶段 init 中加载任何固件,尤其是在设备探测上下文中。如果第一阶段 ramdisk 中没有固件,在第一阶段 init 中加载固件就会导致整个启动过程停滞。即使第一阶段 ramdisk 中有固件,也会导致不必要的延迟。
七、Qcom平台的GKI 2.0方案
7.1 gki config叠加规则

7.2 GKI2.0后高通平台基线升级boot.img合入
7.2.1 boot.img/vmlinux下载
boot.img在GKI后完全有google提供,因此我们又自己的修改需要单独给google提patch,由google编译完成后,我们再下载,每次基线升级后的boot.img也需要我们去单独下载合入
基线升级boot.img下载的方式:
7.2.1.1 从基线升级的Release Note中找到以下 的相关

找到TAG:android13-5.15-2022-08_r7
Vmlinux download link:https://ci.android.com/builds/submitted/9096577/kernel_aarch64/latest
主线发布网址:https://source.android.com/docs/core/architecture/kernel/gki-android13-5_15-release-builds
7.2.1.2 下载boot.img

进主线发布网址https://source.android.com/docs/core/architecture/kernel/gki-android13-5_15-release-builds,搜索tag:android13-5.15-2022-08_r7
7.2.1.2.1 boot.img

7.2.1.2.2 userdebug_boot.img

7.2.1.2.3 vmlinux
进入vmlinux download link:https://ci.android.com/builds/submitted/9096577/kernel_aarch64/latest
搜素vmlinux

保证这三者三码一致

7.2.2 合入代码
7.2.2.1 新建device/xiaomi/gki_images仓库
发邮件给scm创建该项目分支的gki_images仓库
7.2.2.2 代码合入规则
若有新的QKI升级,需要依次执行本规范中的动作:
1. 需要保证以下一整套QKI文件同时更新:
boot.img(此文件就是QKI)
vmlinux.zip(QKI对应的vmlinux)
2. 更新本目录中的升级历史文件 —— 2_UpdateHistory.txt
- 将下载到的boot-5.15.img重命名为boot.img
vmlinux压缩为vmlinux.zip(在云桌面操作)
- 修改 2_UpdateHistory.txt
八、MTK平台的GKI 2.0方案
https://online.mediatek.com/QuickStart/QS00297#QSS03661
九、参考文档
https://source.android.com/devices/architecture/kernel/generic-kernel-image#gki-1_0-compatibility-req(通用内核映像)
https://source.android.google.cn/docs/core/architecture/kernel/android-common(Android Common Kernels)
https://blog.csdn.net/feelabclihu/article/details/113409593 (GKI改造原则、机制和方法)
80-13043-3 Android Generic Kernel Image
80-13043-4 Android Generic Kernel Image 2.0
80-13043-5 Qualcomm Kernel Image (QKI) Overview
KBA-201222232647 Build GKI with console enabled
KBA-210114010438