Android GKI 2.0

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.4android12-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.10android12-5.10android13-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

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 有一些开发准则需要遵守,以下几点是比较容易遇到的:

  1. 可以使用 MODULE 变量。

    当模块被编译为 ko 时,MODULE 变量为 true,built-in 时为 false。所以可以使用 #ifdef MODULEifndef MODULE 来进行 ko 和 built-in 的区分。

  2. 使用 #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)时,则不会有结果。
  3. ko 调用的接口一定得是 ABI symbol list 中的,否则会编译报错。

  4. 开机阶段不需要使用的 ko 尽量放在第二阶段 init 加载,放在第一阶段加载会影响开机时间。

  5. 注意 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 相同的架构。

  1. 从 Android 13 起,引入 init_boot 分区,因此 generic ramdisk 从 GKI boot 移至 init_boot ,此时 bootimage 只有 GKI kernel。
  2. 设备未使用 recovery 分区,recovery 资源会从通用 ramdisk 移至 vendor_boot ramdisk;设备使用 recovery 分区,recovery ramdisk 是单独的。
  3. generic ramdisk 和 vendor_boot ramdisk 组合成完整的 ramdisk。

在这里插入图片描述
在这里插入图片描述

GKI 是 Google 致力推行的,以后遇到的项目基本上都是 GKI 的,因此简单学习一下 GKI 相关的一些知识。目的在于了解,所以很多内容未深入学习。文章多是一些基础概念,未涉及开发,在后续会根据平台梳理 GKI 开发的相关内容。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值