在 Android 8.0 中,设备内核分为系统芯片 (SoC)、设备和板专属组件。基于这种分层结构的内核和 Android 使得原始设计制造商 (ODM) 和原始设备制造商 (OEM) 可以在独立的板专属树中使用板专属功能、驱动程序等,使他们可以替换通用的内核配置、以内核模块的形式添加新的驱动程序等。

本页详细介绍了以下方面的要求:

可加载的内核模块

所有 SoC 内核都必须支持可加载的内核模块。作为着手点,以下内核配置选项(或其内核版本等效项)已添加到所有通用内核中的 android-base.cfg,且必须在所有设备内核中启用:

CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODVERSIONS=y

内核模块应尽可能支持卸载和重新加载。

模块签名

(可选)ODM 可以启用以下内核配置选项,以在其自己的内核配置中启用模块签名:

CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y

在需要支持验证启动的设备上,Android 要求内核模块位于启用 dm-verity 的分区中。模块签名并非强制性要求,也不会进行测试;不过,如有需要,ODM 也可以启用模块签名,但前提是 ODM 拥有所需的密钥签名及其他基础架构,以确保未来可进行独立的内核和文件系统 OTA 更新。

文件位置

Android 7.x 及更早版本对内核模块(包括对 insmodrmmod 的支持)没有强制要求,而 Android 8.x 及更高版本建议在生态系统中使用内核模块。下表显示了 Android 的 3 种启动模式所需的潜在板专用外设支持:

启动模式 存储 显示 拨号键盘 电池 PMIC 触摸屏 NFC、WLAN、
蓝牙
传感器 相机
恢复
充电
Android

除了按 Android 启动模式的可用情况对内核模块进行分类之外,还可以按照所有者(SoC 供应商或 ODM)进行分类。如果使用了内核模块,则它们在文件系统中的放置位置的要求如下:

在 Android 7.x 及更早版本中,/vendor/odm 分区不会提前装载。在 Android 8.x 及更高版本中,为使模块能够从这些分区加载,已进行相关配置,以便为非 A/B 和 A/B 设备提前装载分区。这还确保了在 Android 和充电模式下均装载分区。

Android 编译系统支持

BoardConfig.mk 中,Android 编译系统定义了 BOARD_VENDOR_KERNEL_MODULES 变量,该变量提供了用于供应商映像的内核模块的完整列表。此变量中列出的模块会被复制到位于 /lib/modules/ 的供应商映像中,在 Android 中装载后会显示在 /vendor/lib/modules 中(根据上述要求)。下面是一个供应商内核模块的配置示例:

vendor_lkm_dir := device/$(vendor)/lkm-4.x
BOARD_VENDOR_KERNEL_MODULES := \
  $(vendor_lkm_dir)/vendor_module_a.ko \
  $(vendor_lkm_dir)/vendor_module_b.ko \
  $(vendor_lkm_dir)/vendor_module_c.ko

…其中,供应商内核模块预构建代码库会映射到 Android 编译系统中的上述位置。

恢复映像可能包含供应商模块的子集。Android 编译系统定义了这些模块的变量 BOARD_RECOVERY_KERNEL_MODULES。例如:

vendor_lkm_dir := device/$(vendor)/lkm-4.x
BOARD_RECOVERY_KERNEL_MODULES := \
  $(vendor_lkm_dir)/vendor_module_a.ko \
  $(vendor_lkm_dir)/vendor_module_b.ko

Android 编译系统负责运行 depmod 以在 /vendor/lib/modules/lib/modules (recovery ramfs) 中生成所需的 modules.dep 文件。

模块加载和版本管理

我们建议通过调用 modprobe -ainit.rc* 一次加载所有内核模块。这样可以避免重复初始化 modprobe 二进制文件的 C 运行时环境产生的开销。您可以修改 early-init 事件来调用 modprobe

on early-init
    exec u:r:modprobe:s0 -- /vendor/bin/modprobe -a -d \
        /vendor/lib/modules module_a module_b module_c ...

通常,内核模块必须使用将与该模块结合使用的内核进行编译,否则,内核会拒绝加载该模块。 CONFIG_MODVERSIONS 通过检测 ABI 中的损坏情况提供了一种解决方案。该功能会计算内核中每个导出的符号的原型的循环冗余校验 (CRC) 值,并将这些值作为内核的一部分进行存储;对于内核模块所用的符号,相应的值也会存储在内核模块中。模块加载完成后,模块所用符号的值将与内核中的相应值进行比较。如果这些值相互匹配,则加载模块;如果不匹配,则模块加载会失败。

要使内核映像的更新独立于供应商映像,请启用 CONFIG_MODVERSIONS。这样做可以在确保与供应商映像中的现有内核模块保持兼容的同时,对内核进行小幅度更新(例如 LTS 提供的问题修复)。不过,CONFIG_MODVERSIONS 本身并不会修复 ABI 损坏。如果内核中某个导出的符号的原型由于源代码的修改或内核配置更改而发生变化,则会破坏与使用该符号的内核模块的兼容性。在此类情况下,必须重新编译内核模块。

例如,内核中的 task_struct 结构(在 include/linux/sched.h 中定义)包含很多字段,具体包含的字段根据相关条件取决于内核配置。sched_info 字段仅在 CONFIG_SCHED_INFO 启用(启用 CONFIG_SCHEDSTATSCONFIG_TASK_DELAY_ACCT 时发生)时才显示。如果这些配置选项的状态发生变化,task_struct 结构的布局也将发生变化,同时,从内核导出的使用 task_struct 的所有接口都会发生变化(如 kernel/sched/core.c 中的 set_cpus_allowed_ptr)。与使用这些接口的之前编译的内核模块的兼容性将会被破坏,这就需要使用新的内核配置重新编译这些模块。

要详细了解 CONFIG_MODVERSIONS,请参阅位于 Documentation/kbuild/modules.txt 的内核树中的相关文档。

提前装载分区(第一阶段装载)

 必须执行的工作 

所有支持 Treble 的设备都必须启用第一阶段装载,以确保 init 可以加载分布在 systemvendor 分区的 SELinux 政策片段(这样还可实现在内核启动后尽快加载内核模块)。

Android 必须有权访问模块所在的文件系统。为此,Android 8.x 及更高版本支持在 init 的第一阶段(即初始化 SELinux 之前)装载 /system/vendor/odm。设备制造商可以使用设备树叠加层为提前装载的分区指定 fstab 条目。

提前装载分区 (VBoot 1.0)

使用 VBoot 1.0 提前装载分区的要求包括:

  1. 设备节点路径必须在 fstab 和设备树条目中使用其 by-name 符号链接。例如,确保对分区进行命名且设备节点为 /dev/block/…./by-name/{system,vendor,odm},而不是使用 /dev/block/mmcblk0pX 指定分区。
  2. 在产品的设备配置中(即 device/oem/project/device.mk 中)为 PRODUCT_{SYSTEM,VENDOR}_VERITY_PARTITIONCUSTOM_IMAGE_VERITY_BLOCK_DEVICE 指定的路径必须与 fstab 设备树条目中相应块设备节点指定的 by-name 相匹配。例如:
    PRODUCT_SYSTEM_VERITY_PARTITION := /dev/block/…./by-name/system
    PRODUCT_VENDOR_VERITY_PARTITION := /dev/block/…./by-name/vendor
    CUSTOM_IMAGE_VERITY_BLOCK_DEVICE := /dev/block/…./by-name/odm
    
  3. 通过设备树叠加层提供的条目不得在 fstab 文件片段中出现重复。例如,指定某个条目以在设备树中装载 /vendor 时,fstab 文件不得重复该条目。
  4. verifyatboot提前装载需要 verifyatboot 的分区(此操作不受支持)。
  5. 必须在内核命令行中使用 androidboot.veritymode 选项指定验证分区的真实模式/状态(现有要求)。

提前装载设备树 (VBoot 1.0)

在 Android 8.x 及更高版本中,init 会解析设备树并创建 fstab 条目,以在其第一阶段提前装载分区。fstab 条目采取以下形式:

src mnt_point type mnt_flags fs_mgr_flags

定义设备树属性以模拟该格式:

示例:N6P 上的 /system 和 /vendor

下面的示例显示的是在 Nexus 6P 上为 systemvendor 分区提前装载设备树:

/ {
  firmware {
    android {
      compatible = "android,firmware";
  fstab {
    compatible = "android,fstab";
    system {
      compatible = "android,system";
      dev = "/dev/block/platform/soc.0/f9824900.sdhci/by-name/system";
      type = "ext4";
      mnt_flags = "ro,barrier=1,inode_readahead_blks=8";
      fsmgr_flags = "wait,verify";
    };
    vendor {
      compatible = "android,vendor";
      dev = "/dev/block/platform/soc.0/f9824900.sdhci/by-name/vendor";
      type = "ext4";
      mnt_flags = "ro,barrier=1,inode_readahead_blks=8";
      fsmgr_flags = "wait";
    };
      };
    };
  };
};

示例:Pixel 上的 /vendor

下面的示例显示的是在 Pixel 上为 /vendor 提前装载设备树(请务必为 A/B 分区添加 slotselect):

/ {
  firmware {
    android {
      compatible = "android,firmware";
      fstab {
        compatible = "android,fstab";
        vendor {
          compatible = "android,vendor";
          dev = "/dev/block/platform/soc/624000.ufshc/by-name/vendor";
          type = "ext4";
          mnt_flags = "ro,barrier=1,discard";
          fsmgr_flags = "wait,slotselect,verify";
        };
      };
    };
  };
};

提前装载分区 (VBoot 2.0)

VBoot 2.0 是 Android 验证启动 (AVB)。 使用 VBoot 2.0 提前装载分区的要求如下:

  1. 设备节点路径必须在 fstab 和设备树条目中使用其 by-name 符号链接。例如,确保对分区进行命名且设备节点为 /dev/block/…./by-name/{system,vendor,odm},而不是使用 /dev/block/mmcblk0pX 指定分区。
  2. VBoot 1.0 所用的编译系统变量(如 PRODUCT_{SYSTEM,VENDOR}_VERITY_PARTITIONCUSTOM_IMAGE_VERITY_BLOCK_DEVICE)对 VBoot 2.0 而言并非必需的。相反,您应该定义 VBoot 2.0 中引入的新编译系统变量(包括 BOARD_AVB_ENABLE := true);有关完整配置,请参阅适用于 AVB 的编译系统集成
  3. 通过设备树叠加层提供的条目不得在 fstab 文件片段中出现重复。例如,如果您指定某个条目以在设备树中装载 /vendor,则 fstab 文件不得重复该条目。
  4. VBoot 2.0 不支持 verifyatboot,无论是否启用了提前装载。
  5. 必须在内核命令行中使用 androidboot.veritymode 选项指定验证分区的真实模式/状态(现有要求)。 确保包含以下 AVB 修复程序:

提前装载设备树 (VBoot 2.0)

VBoot 2.0 设备树中的配置与 VBoot 1.0 中的大致相同,但还有以下几项不同之处:

示例:N5X 上的 /system 和 /vendor

下面的示例显示的是在 Nexus 5X 上为 systemvendor 分区提前装载设备树。请注意:

示例:Pixel 上的 /vendor

下面的示例显示的是在 Pixel 上提前装载 /vendor。 请注意:

设备树叠加层支持(引导加载程序)

设备树叠加层 (DTO) 旨在扩展现有扁平化设备树 (FDT) 的实现,以便用户空间(在运行时)可通过加载修改了原始数据的额外叠加层 FDT 来修改内核中的初始设备树数据。Android 不需要在运行时更新用户空间的 DT Blob,而是建议供应商借助 libfdt/libufdt 在引导加载程序中添加设备树补丁程序。

Android 对 DTO 的支持因 Android 版本而异:

分区要求

目前,大多数 Android 设备都在编译时将 DT Blob 附加到内核中,而引导加载程序知道如何从内核读取 DT Blob。由于 Android 对如何编译/存储 DT Blob(被视为 SoC 内核的一部分)没有特定要求,因此 DT Blob 可以附加到内核中,也可以将其单独存储在某个分区中。这里唯一的假设前提是引导加载程序已经知道如何加载以及从何处加载 DT Blob。

设备树叠加层支持(如果使用)的要求如下:

引导加载程序要求

引导加载程序的要求包括:

要详细了解如何在引导加载程序中增加对 DTO 的支持,请参阅设备树叠加层

核心内核要求

自 Android 8.0 开始,Android 规定了最低内核版本和内核配置,且会在 VTS 中以及 OTA 期间对它们进行检查。Android 设备内核必须启用内核 .config 支持以及在运行时通过 procfs 读取内核配置的选项。

内核 .config 支持

所有设备内核都必须完整启用 android-base.cfg,其中必须包含以下内核配置选项(或其内核版本等效选项):

CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y

内核版本

对于 Android 9,要求的最低 LTS 内核版本是 4.4.107、4.9.84 和 4.14.42。

要详细了解 LTS 内核,请参阅长期稳定的内核Android 通用内核

设备树支持

内核中必须启用设备树支持,且引导加载程序必须将硬件描述以设备树的形式传递给内核(除非平台支持 ACPI)。设备树还必须可供 Android 读取,且能够将供应商/ODM 特有的参数传递给 Android。 CONFIG_OF(以及所有其他设备/子系统专用的 CONFIG_OF_* 内核配置选项)是强制性的选项。

3.15 版之前的内核需要提供 CONFIG_PROC_DEVICETREE,以便 Android 在启动初期就能访问供应商/ODM 特有的配置。在 3.15 版及更高版本的内核中,该选项的功能已并入 CONFIG_OF 中。

CONFIG_OF=y
CONFIG_PROC_DEVICETREE=y (kernels prior to 3.15)

要查看使用设备树提前装载 vendor/odm 分区的示例,请参阅 AOSP 变更列表

DebugFS

供应商接口的实现不应依赖 debugfs,您可以启用 debugfs,但在不装载 debugfs 的情况下也可以完成 VTS 测试。

未来的 Android 版本

当前的 Android 版本建议以设备中的内核模块的形式编译和推出所有板专属代码。对 Android 而言,内核的其余部分为一个整体(无论它实际上是单片内核,还是其中的一部分是作为内核模块编译的)。

该单片内核是可以在 SoC 供应商的参考硬件上启动的 SoC 内核,但仅限于此。如今,对 SoC 内核的处理方式与通用内核类似;SoC 内核在板专属的代码库中会有大量副本。这种分发模型会导致,针对每个分支中的同一错误,系统会采取极为不同的方式修复 SoC 内核;这样一来,由于会在不同的时间择优挑选或修复同一错误的方式不同,未来的内核更新会有延迟。要解决此问题,必须单独提供 SoC 内核,以便使用 SoC 的每个人都可以为同一 SoC 内核做贡献。

图 1(下图)是一个常见示例,显示了 SoC 内核如何随着时间的推移在各个 Android 版本以及 ODM 之间逐渐碎片化。

图 1. 设备内核副本

图 1 表明:

  1. 每个人都需要花费大量的时间和精力在板专属分支/标签之间进行交叉合并。
  2. 等待交叉合并的同时,Android 设备制造商会修补他们自己的内核以获取错误/安全漏洞修复程序。
  3. 与父级的偏离导致未来的升级/合并变得非常困难。

针对通用 SoC 内核提议的模型可解决上行合并更改(如 SoC 专属错误修复程序、LTS 升级、安全漏洞修复程序等)导致的问题。 图 2(下图)显示了工作流程在按 SoC 和内核进行过统一的理想场景中如何变化:

图 2. Android 8.x 及更高版本的设备内核

这种方法旨在通过推荐并与设备制造商展开协作以采用最新的通用 SoC 内核,解决内核代码库碎片化的问题。Android 8.x 及更高版本为 ODM 提供了各种可能的选项,使 ODM 不需要维护自己的 SoC 内核,而是依赖通用的 SoC 内核来获取 LTS 升级/问题修复程序/安全漏洞补丁程序等。

我们初步的计划是推动所有 ODM/供应商都使用单一的 SoC 内核源。未来,我们计划朝着每个 SoC 分发单个内核二进制文件的方向发展。

上游化

为了更轻松且近乎自动地更新为较新的内核版本,并为 ODM 提供更安全可靠的平台来开发产品,我们强烈建议 SoC 供应商将其内核更改上游化,并使它们被 kernel.org 主代码库接受。虽然这样做需要前期投入额外的时间和工程资源,但事实证明,从长远的角度来看,这样会节省时间和资金。另据证实,与未经社区审核的代码相比,合并后的代码质量高很多,错误和安全漏洞问题也更少(这些错误和安全漏洞问题的严重程度通常更低)。

如果将对 SoC 的全面支持合并到上游,社区便可以在内部的内核 API 随时间不断发展的同时,做出必要的 API 更改,从而自动延长平台的使用寿命。通过将硬件平台添加到诸多社区管理的内核测试平台的其中一个(如 kernelci.org),还可以在开发和稳定版本中针对任何回归自动测试内核。

要获取与 Linux 内核社区协作以将您的代码上游化的相关帮助,请参阅以下资源:

社区会稍作审核就接受独立的驱动程序和文件系统,并将其纳入内核的暂存区,然后在其中努力提高代码质量。