GKI 背景
Android common kernels 是所有 Android 产品内核的基础。供应商和设备商的内核都位于 ACK 的下游。供应商通过修改内核源代码和添加设备驱动程序来添加对 SoC 和外围设备的支持。因此在早期我们设备产品上运行的代码可能有多达 50% 都来源于树外代码,并非来源于上游 Linux kernel 社区和 AOSP common kernel。
因此,设备内核由以下部分组成:
- Upstream: The Linux kernel from kernel.org
- AOSP: Additional Android-specific patches from AOSP common kernels
- Vendor: SoC and peripheral enablement and optimization patches from vendors
- OEM/device: Additional device drivers and customizations
几乎每一个设备都有一个自定义内核,这也就导致了内核碎片化的问题。
内核碎片化带来的问题:
- 安全更新需要耗费大量人力
- 难以 merge LTS 内核的更新
- 妨碍 Android 平台进行版本升级
- 难以将内核修改贡献给上游内核 (kernel.org)
为了解决内核碎片化问题,Google 引入了 Generic Kernel Image (GKI) 。
在了解 GKI 之前,需要先了解一下 Android Comon Kernel。
Kernel
Android Common Kernel
早期 Android kernel 来源于上游 Linux Long Term Supported (LTS) kernel 。LTS kernel 和 Android-specific patches 结合,形成我们所熟知的 Android common kernels (ACK) 。Android-specific patches 主要指的是:
- Android 功能所需的上游功能的 backports 和 cherry-picks。
- 适用于 Android 设备但仍在上游开发的功能(例如,Energy Aware Scheduler 任务放置优化)。
- Vendor/OEM features,这些 features 可能也适用于其他合作厂商(例如 sdcardfs)。
android-mainline
是 Android 功能的主要开发分支。每当 Linus Torvalds 发布一个版本或候选版本时,Linux 主线就会合并到 android-mainline
中。
当上游声明一个新的 LTS 时,相应的 common kernel 就会从 android-mainline
分支创建。
下图是 android common kernel 的层次结构。
我们主要关注 GKI 相关的,以 android 12 S 为例,Google 会选用 LTS kernel 5.10 作为 android common kernel,然后从 android-mainline 拉出新的分支 KMI 内核分支 android12-5.10
。在 Android 13 发布后,为了开发适用于 Android13 的功能,会从 android12-5.10
拉出新的分支 android13-5.10
。
一些术语:
Android Common Kernel (ACK)
ACK 是长期支持 (LTS) 内核的下游,包含与 Android 社区相关但尚未合并到 Linux Mainline 或 LTS 内核的补丁。较新的 ACK(版本 5.4 及更高版本)也称为 GKI 内核,因为它们支持将与硬件无关的通用内核代码和与硬件无关的 GKI 模块分离开来。
Android Open Source Project (AOSP) kernel
Android common kernel。
Feature Kernel
保证实现平台发布功能的内核。例如,在 android 12 上有两个 feature kernel:
- android12-5.4
- android12-5.10
generic core kernel
GKI kernel 中的通用部分。
Generic Kernel Image (GKI) kernel
较新的(5.4 及更高版本)ACK 内核(当前仅限 aarch64)。GKI kernel 由两部分组成:
- core GKI kernel:核心 GKI 内核,其代码在所有设备上通用;
- GKI modules:由 Google 开发,可以在设备上动态加载。
Kernel Module Interface (KMI) kernel
GKI 内核为内核模块提供了稳定的内核模块接口 KMI,所以我理解 KMI kernel 就是带有 KMI 接口的 kernel。
Generic Kernel Image (GKI)
通用内核镜像
Kernel Module Interface (KMI)
GKI 内核与供应商模块之间的接口。使供应商模块能够独立于 GKI 内核进行更新。此接口包括已根据合作伙伴符号列表标识为供应商/OEM 依赖项的内核函数和全局数据。
Dynamically loadable kernel module (DLKM)
可以在设备启动过程中根据设备需求动态加载的模块。GKI 和供应商模块都是 DLKM。DLKM 以
.ko
形式发布,可以是驱动程序,也可以提供其他内核功能。
Generic Kernel Image
为了解决内核碎片化问题,Google 引入了 Generic Kernel Image (GKI) 。GKI 项目通过统一核心 kernel 并将 SoC 和板级支持从核心 kernel 移至可加载模块中,解决了内核碎片化的问题。
GKI 内核为内核模块提供了稳定的内核模块接口 (Kernel Module Interface:KMI),因此模块和内核可以独立进行更新。
GKI 具有以下特点:
- GKI kernel 是由 ACK 源码编译构建的。
- GKI 内核是一个单内核二进制文件,加上每个架构、每个 LTS 版本的相关可加载模块。
- GKI 内核已经过相关 ACK 支持的所有 Android 平台版本的测试。 GKI 内核版本的生命周期内不会弃用任何功能。
- 为给定 LTS 中的驱动程序提供了稳定版 KMI。
- 不包含 SoC 专用代码或板卡专用代码。
Google 定义 KMI 接口把 GKI 以外的其他 module(包括 kernel 原生 module 和 vendor 自定义 module)以 ko 等形式全部分离到 GKI 以外,模块和内核可以独立更新,真正做到了 kernel core 和 driver module 的去耦合化,大幅降低了更新 kernel core 和 driver 的 efforct,同时也会大幅降低未来 SOC/OEM 升级 kernel 大版本时的工作量。
下图是 GKI 的架构
当前 GKI 的实现有两个阶段:
- GKI 1.0:在 Android 11 中引入的,适用于 kernel 5.4 的设备。 GKI 1.0 适用于所有搭载 5.4 内核的设备,甚至是搭载 Android 12 或 Android 13 的设备
- GKI 2.0:在 Android 12 中针对 kernel 5.10 的设备引入的,并且是所有搭载 5.10 或更高版本内核的设备的新标准。
ACK KMI 内核分支
GKI 内核有一个稳定的 Kernel Module Interface (KMI)。KMI 由内核版本和 Android 平台版本唯一标识,因此分支命名为:<androidRelease>-<kernel version>
。例如 android 12 有两个 GKI 内核:android12-5.4
和 android12-5.10
。
在 Android common kernel 部分有介绍 Android common kernel 的层次结构。在 android 12 版本,从 android-mainline 拉出 KMI 分支 android12-5.10。简单了解 ACK KMI 分支的生命周期。
如上图所示,每一个 ACK KMI 分支都会经历三个阶段,在图中用不同颜色标记。无论处于哪个阶段,LTS 都会定期 merged。
KMI 分支三个阶段分别为:
-
开发阶段(Development phase)
KMI 分支创建后,KMI 分支进入开发阶段。如图,dev 对应的就是开发阶段。在图中,5.10 LTS 发布后被 Android 选用作为 common 分支,并创建
android12-5.10
ACK kernel 分支。未了兼容新的 Android 版本功能,还会基于android12-5.10
创建新的 ACK kernel 分支android13-5.10
。 -
稳定阶段(Stabilization phase)
当 ACK KMI 分支声明的功能都完成时,进入稳定阶段。在图中,stable 对应的是稳定阶段。三方的 features 和 bug fixes 仍然会被接受,但是 KMI tracking 会被使能并且检测影响 KMI 接口的修改。在此阶段,接受破坏 KMI 接口的修改,必须要更新 KMI 接口定义。
-
冻结阶段(Frozen phase)
在新平台版本推送到 AOSP 之前,ACK KMI 分支会被冻结,并在分支的生命周期内保持冻结状态。这意味着,除非发现严重的安全问题,且无法在不影响稳定 KMI 的情况下缓解,否则不会接受任何破坏 KMI 的更改。为了避免 KMI 损坏,如果 Android 设备不需要修复,则可能会修改或删除从 LTS 合并的一些补丁。
此阶段有些要求,例如,不允许向 KMI 接口 common kernel 使用的结构体添加字段,因为它更改了接口的定义:
struct foo { int original_field1; int original_field2; int new_field; // Not allowed }; int do_foo(struct foo &myarg) { do_stuff(myarg); } EXPORT_SYMBOL_GPL(do_foo);
但是可以借用现有接口,添加一个新的 function:
struct foo2 { struct foo orig_foo; int new_field; }; int do_foo2(struct foo2 &myarg) { do_stuff2(myarg); } EXPORT_SYMBOL_GPL(do_foo2);
Maintain a stable KMI
GKI 项目下,GKI kernel 以二进制文件的形式编译和发布,这个二进制文件也就是我们所说的 boot.img。供应商部分作为 loadable modules,以 ko 的形式单独编译。因此必须要为供应商模块维护一个稳定的 KMI 接口,以保障 GKI kernel 和 vendor modules 如一同编译般正常工作、稳定运行。
面对不同的工具链、配置和不断发展的 Linux Mainline 内核,在 Mainline 中维持稳定版 KMI 并不可行。但是,在高度约束的 GKI 环境中,借助以下约束条件或许能维持稳定的 KMI:
- 只能使用一个配置
gki_defconfig
来构建内核。 - KMI 仅在一个内核(例如
android13-5.10
、android12-5.10
或android13-5.15
)的相同 LTS 和 Android 版本内保持稳定。不对android-mainline
维持 KMI 稳定性。 - 只使用 AOSP 中提供的并且为相应分支指定的特定 Clang 工具链来构建内核和模块。
- 只有在 symbol list 中指定的已知由模块使用的符号才会受到稳定性监控,并被视为 KMI symbols。供应商模块只能使用 KMI symbols。
- KMI 分支被冻结后,可以进行更改,但不能破坏 KMI。这些更改包括:
- Config changes
- kernel 代码 changes
- 工具链 changes(包括更新)
kernel/manifest
完整的描述了构建一个稳定的 KMI 所需要的编译环境。例如 [android13-5.15](default.xml - kernel/manifest - Git at Google (googlesource.com)) 的 manifest,定义了 toolchain、build scripts 以及构建 GKI kernel 所需要的其他内容。
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<remote name="aosp" fetch=".." review="https://android-review.googlesource.com/" />
<default revision="master-kernel-build-2022" remote="aosp" sync-j="4" />
<superproject name="kernel/superproject" remote="aosp" revision="common-android13-5.15" />
<project path="build/kernel" name="kernel/build" >
<linkfile src="kleaf/bazel.sh" dest="tools/bazel" />
<linkfile src="kleaf/bazel.WORKSPACE" dest="WORKSPACE" />
<linkfile src="build.sh" dest="build/build.sh" />
<linkfile src="build_abi.sh" dest="build/build_abi.sh" />
<linkfile src="build_test.sh" dest="build/build_test.sh" />
<linkfile src="build_utils.sh" dest="build/build_utils.sh" />
<linkfile src="config.sh" dest="build/config.sh" />
<linkfile src="envsetup.sh" dest="build/envsetup.sh" />
<linkfile src="_setup_env.sh" dest="build/_setup_env.sh" />
<linkfile src="multi-switcher.sh" dest="build/multi-switcher.sh" />
<linkfile src="abi" dest="build/abi" />
<linkfile src="static_analysis" dest="build/static_analysis" />
</project>
<project path="common" name="kernel/common" revision="android13-5.15" >
<linkfile src="." dest=".source_date_epoch_dir" />
</project>
<project path="kernel/tests" name="kernel/tests" />
<project path="kernel/configs" name="kernel/configs" />
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="android13-5.15" />
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" clone-depth="1" />
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" />
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" />
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" />
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" />
<project path="prebuilts/bazel/linux-x86_64" name="platform/prebuilts/bazel/linux-x86_64" clone-depth="1" />
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" clone-depth="1" />
<project path="prebuilts/ndk-r23" name="toolchain/prebuilts/ndk/r23" clone-depth="1" />
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" />
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" />
<project path="external/stardoc" name="platform/external/stardoc" />
<project path="external/python/absl-py" name="platform/external/python/absl-py" />
</manifest>
Android Kernel ABI Monitoring
为了维护 GKI 项目的稳定性,symbols 也受 GKI 管控,所有可共 modules 使用的 symbols 都被明确的列在一组 symbol list 文件中,这些文件在内核树的根目录中公开维护。所有 symbol list 文件中所有符号的并集定义了保持稳定的 KMI symbols 集。
例如在 android11-5.4 中,以下 symbol list 文件中的 symbol 定义了 android13-5.15 的 KMI symbols集。
abi_gki_aarch64
abi_gki_aarch64.xml
abi_gki_aarch64_amlogic
abi_gki_aarch64_db845c
abi_gki_aarch64_exynos
abi_gki_aarch64_exynos_wear
abi_gki_aarch64_exynosauto
abi_gki_aarch64_fips140
abi_gki_aarch64_galaxy
abi_gki_aarch64_general
abi_gki_aarch64_honor
abi_gki_aarch64_imx
abi_gki_aarch64_lenovo
abi_gki_aarch64_mtk
abi_gki_aarch64_mtktv
abi_gki_aarch64_oplus
abi_gki_aarch64_pasa
abi_gki_aarch64_pixel
abi_gki_aarch64_qcom
abi_gki_aarch64_rtktv
abi_gki_aarch64_sony
abi_gki_aarch64_sunxi
abi_gki_aarch64_tuxera
abi_gki_aarch64_type_visibility
abi_gki_aarch64_unisoc
abi_gki_aarch64_virtual_device
abi_gki_aarch64_virtual_device_removed
abi_gki_aarch64_vivo
abi_gki_aarch64_xiaomi
abi_gki_aarch64_zeku
abi_gki_modules_exports
abi_gki_modules_protected
gki_aarch64_fips140_modules
gki_aarch64_modules
gki_system_dlkm_modules
以上这些文件也就是我们常说的 whitelist。
只有 symbol list 中列出的 symbol 及其相关结构和定义才被视为 KMI 的一部分。假如使用的内核函数接口没有在 symbol list 中,编译时因为找不到定义报错。因此,当需要使用的符号不存在于 symbol list 中,此时需要向 Google 申请在 symbol list 中加入新的 symbol。
下面是 abi_gki_aarch64_db845c 文件中截取的 symbol。
# required by ufs_qcom.ko
phy_set_mode_ext
ufshcd_dme_configure_adapt
ufshcd_dme_get_attr
ufshcd_dme_set_attr
ufshcd_dump_regs
ufshcd_get_local_unipro_ver
ufshcd_get_pwr_dev_param
ufshcd_init_pwr_dev_param
ufshcd_pltfrm_init
ufshcd_pltfrm_shutdown
ufshcd_remove
ufshcd_resume_complete
ufshcd_runtime_resume
ufshcd_runtime_suspend
ufshcd_suspend_prepare
ufshcd_system_resume
ufshcd_system_suspend
ufshcd_uic_hibern8_enter
ufshcd_uic_hibern8_exit
ABI(Application Binary Interface) Monitoring tooling 使用 KMI symbol list 来限制必须监视哪些接口以确保稳定性。该工具从现有内核二进制文件(vmlinux+modules)收集并比较 ABI representations ,这些 ABI representations 指的是 .stg
文件和 symbol lists。工具主要监控是否有破快 ABI 的行为,例如前面所说的更改 KMI 接口定义等。
如果现有 ABI 不满足需求,扩展也无法满足需求,那就需要走流程修改 ABI。
GKI 发布
GKI 在 KMI 冻结后按月发布。
我们可以在 Google android 网站查询和获取已发布的 GKI boot 以及发布时间。
当前已发布的 GKI Release build:
- android12-5.10 Releases
- android13-5.10 Releases
- android13-5.15 Releases
- android14-5.15 Releases
- android14-6.1 Releases
GKI 月度发布包含经过测试的boot.img
,其中包含 Google 插入的证书,以证明二进制文件是根据已知源代码基线构建的。
每个月,在签入截止日期之后选择一个 GKI 月度发布候选者(未认证),这通常是该月的第二个每周构建。选择每月发布候选后,**新的更改将不会被接受到该月的发布中。**在关闭窗口期间,只能修复导致测试失败的错误。
如何理解?
比如当前 GKI release 是 android12-5.10
,以 2023 年 2 月发布的版本为例。Android 于 2023-02-25 日发布第一个版本,以此时间节点拉出月度分支 android-5.10-2023-02
,之后该月度分支不在合入新的修改。当然一些安全 patch 或者是解决测试失败的 patch 会被合入该月度分支。
下面是 android-5.10-2023-02
月度分支发布的 build。
假设当前项目使用的 boot 是2023-03-14 发布的 android12-5.10-2023-02_r2 ,如果在使用该 boot 时,测试出问题怎么办?
一般来说,首先要确认当前 bug 是否已经被修复,如果没有修复可以跟平台提 bug。如果已经修复,可以看看当前月度分支是否已经入库此修改,在没有入库此修改时,我们有以下两个选择:
- 使用已经合入修改的 boot。
- 在当前月度分支上申请 respin 流程。
使用已经合入修改的 boot 很好理解,比如在 4 月份发布的 boot 已经合入修改,我们就拿 4 月份的 boot。但是这个时候也会有一定风险,毕竟每个月都会有大量的修改被合入主基线,我们没法评估这些修改在带入本地后会不会引入一些其他问题。因此,我们绝大多时候会选择 respin。
Emergency respin process
Respin 是在GKI 内核公开发布后重新合并、重建、重新测试和重新认证二进制文件的过程。在以下任何情况下,您都可以请求重新制作经过认证的二进制文件:
- 更新符号列表。
- 对错误应用修复,包括在运营商实验室批准期间发现的错误。
- 添加 vendor hook
- 将补丁应用于现有功能。
- 应用安全补丁(6 个月后)。
简单来说,Respin 就是基于已发布的 GKI 构建新的 GKI 镜像。
下图展示的是 respin 的流程:
关于 respin 更多内容,请参考:Emergency Respin process 。
Modules
有两种类型的内核模块:
-
GKI 模块:与硬件无关的 GKI 模块。
GKI 模块用于提供独立于通用 core kernel 的非启动所需的内核功能。GKI 模块有两种逻辑类型:
- Protected GKI module:受保护的 GKI 模块由 Google 提供,不受任何限制。
- Unprotected GKI module:未收保护的 GKI 模块可以被供应商 模块覆盖。
-
Vendor 模块:特定于硬件的供应商模块。
Vendor 模块由 vendor、oem、odm 厂商提供,以实现 SOC 和特定于设备的功能。
启动阶段早期所需要的模块必须要打包到 vendor_boot,当然从 vendor_boot 加载模块会增加系统启动时间。
Modules 可能是 library 库,也可能是 driver 驱动程序。
- Library modules:是提供 API 供其他模块使用的库。此类模块并不是特定于某个硬件的。例如 AES encryption 模块、编译为模块的 Remoteproc 框架以及 logbuffer 模块。模块代码通过
module_init()
运行以创建数据结构,除非被外部模块触发,否则不会运行其他代码。 - Driver modules:probe 或 bind 到特定类型设备的驱动程序。此类模块基本都是特定于硬件的。例如, UART、PCIe,仅当系统上存在其关联的设备时,驱动程序模块才会激活。
- 如果设备不存在,只会调用
module_init
注册。 - 如果设备存在,驱动成功 probe 或者 bind 设备,其他关联模块代码可能也会运行。
- 如果设备不存在,只会调用
在内核开发时,可能会遇到将新功能配置为 GKI 模块或者将内核功能配置为 GKI 模块的问题,方法可以参考 [将内核功能配置为 GKI 模块](将内核功能配置为 GKI 模块 | Android 开源项目 | Android Open Source Project (google.cn)) 。注意,不管是哪种操作,都是在修改 GKI,因此修改最终是需要提交给 Google 审核的。
适配 Google GKI,Soc 板级驱动需要做到:
- 模块 ko 化;
- 与框架层解耦,能够在 Google 通用内核中运行;
- 提交驱动时,原则上不修改内核原生代码。
在驱动开发过程中,GKI 有一些开发准则需要遵守,以下几点是比较容易遇到的:
-
可以使用
MODULE
变量。当模块被编译为 ko 时,MODULE 变量为 true,built-in 时为 false。所以可以使用
#ifdef MODULE
和ifndef MODULE
来进行 ko 和 built-in 的区分。 -
使用
#if IS_ENABLED()
而非#ifdef
判断 CONFIG_ 是否打开。#if IS_ENABLED(CONFIG_XXX)
可以用于三态(m,y,n)判断,#ifdef
只能用于 y/n 判断。- 当
CONFIG_XXX
设置为模块 (=m
) 或 built-in (=y
) 时,#if IS_ENABLED(CONFIG_XXX)
的结果为true
。 - 当
CONFIG_XXX
设置为 built-in (=y
) 时,#ifdef CONFIG_XXX
的结果为true
,但当CONFIG_XXX
设置为模块 (=m
)时,则不会有结果。
- 当
-
ko 调用的接口一定得是 ABI symbol list 中的,否则会编译报错。
-
开机阶段不需要使用的 ko 尽量放在第二阶段 init 加载,放在第一阶段加载会影响开机时间。
-
注意 ko 之间的依赖关系。
Partition
随着 GKI 2.0 的引入,分区也产生了一些变化。
boot
:boot 分区包含 kernel 镜像,使用 mkbootimg 创建。在 Android13 以前,该分区还包含 ramdisk 镜像,但是 Android13 及之后,ramdisk 被迁移到 init_boot 分区,因此该分区只有 kernel image。init_boot
:在 Android13 及之后,该分区包含通用 ramdisk 镜像。vendor_boot
:为了保障GKI boot 的通用性,将所有供应商特定信息均从 boot 分区提取出来,放置于 vendor_boot 分区。vendor_dlkm
:vendor_dlkm 分区专门用于存放 vendor 供应商内核 modules。该分区属于动态分区。odm_dlkm
:该分区专门用于存储 ODM kernel modules。该分区属于动态分区。
参考 Generic Boot Partition ,看看设备启动架构。
下图说明了运行 Android 12 及更高版本的设备的架构。使用 Android 13 启动的设备有一个新的 init_boot 映像,其中包含通用 ramdisk。从 Android 12 升级到 Android 13 的设备使用与 Android 12 相同的架构。
- 从 Android 13 起,引入
init_boot
分区,因此 generic ramdisk 从 GKI boot 移至init_boot
,此时 bootimage 只有 GKI kernel。 - 设备未使用 recovery 分区,recovery 资源会从通用 ramdisk 移至 vendor_boot ramdisk;设备使用 recovery 分区,recovery ramdisk 是单独的。
- generic ramdisk 和 vendor_boot ramdisk 组合成完整的 ramdisk。
GKI 是 Google 致力推行的,以后遇到的项目基本上都是 GKI 的,因此简单学习一下 GKI 相关的一些知识。目的在于了解,所以很多内容未深入学习。文章多是一些基础概念,未涉及开发,在后续会根据平台梳理 GKI 开发的相关内容。