模块化内核要求

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

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

  • 对于独立的 SoC 和 OEM/ODM 内核开发的平台支持。Android O 建议以设备中的内核模块的形式构建和推出所有板专属代码。因此:
  • 供应商测试套件 (VTS) 中支持应用二进制接口 (ABI)/应用编程接口 (API) 测试,以确保指定内核可以运行 Android 开放源代码项目 (AOSP) 框架。
  • 每个 Android 版本的最低内核版本,以及对于生成 Android 供应商接口 (VINTF) 内核对象的支持。

可加载的内核模块

所有 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.0 建议在生态系统中使用内核模块。下表显示了 Android 的 3 种启动模式所需的板专用外设设备支持:

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

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

  • 所有内核都应内置对启动和装载分区的支持。
  • 应从只读分区加载内核模块。
  • 对于需要支持验证启动的设备,应从验证分区加载内核模块。
  • 内核模块不应位于 /system 中。
  • 完整 Android 模式或充电模式所需的 SoC 供应商内核模块应该位于 /vendor/lib/modules 中。
  • 如果存在 ODM 分区,则完整 Android 模式或充电模式所需的 ODM 内核模块应该位于 /odm/lib/modules 中。如果不存在,则这些模块应该位于 /vendor/lib/modules 中。
  • 恢复模式所需的 SoC 供应商和 ODM 内核模块应该位于 /lib/modules 下的恢复 ramfs 中。
  • 如果恢复模式和完整 Android 模式/充电模式都需要某个内核模块,则它应同时位于恢复 rootfs/vendor//odm 分区中(如上所述)。
  • 恢复模式所用的内核模块不应依赖仅位于 /vendor/odm 中的模块,因为这些分区在恢复模式下是没有装载的。
  • SoC 供应商内核模块不应依赖 ODM 内核模块。

在 Android 7.x 及更早版本中,/vendor/odm 分区提前装载。在 Android 8.0 中,为使模块能够从这些分区加载,已进行相关配置,以便为非 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.0 支持在 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. 只能提前装载 /system/odm/vendor。Android 不支持在 init 第一阶段装载其他任何分区。
  5. 不得提前装载需要 verifyatboot 的分区(此操作不受支持)。
  6. 必须在内核命令行中使用 androidboot.veritymode 选项指定验证分区的真实模式/状态(现有要求)。

提前装载设备树 (VBoot 1.0)

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

src mnt_point type mnt_flags fs_mgr_flags

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

  • fstab 条目必须在设备树中的 /firmware/android/fstab 下,且必须将兼容字符串集设为 android,fstab
  • /firmware/android/fstab 下的每个节点都被视为单个提前装载 fstab 条目。节点必须定义以下属性:
    • dev。必须指向代表 by-name.分区的设备节点。
    • type。必须是文件系统类型(如在 fstab 文件中一样)。
    • mnt_flags。必须是装载标记的逗号分隔列表(如在 fstab 文件中一样)。
    • fsmgr_flags。必须是 Android fs_mgr flags 列表(如在 fstab 文件中一样)。
      • A/B 分区必须具有 slotselect fs_mgr 选项。
      • 已启用 dm-verity 的分区必须具有 verify fs_mgr 选项。

示例: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. 只能提前装载 /system/odm/vendor。Android 不支持在 init 第一阶段装载其他任何分区。
  5. VBoot 2.0 不支持 verifyatboot,无论是否启用了提前装载。
  6. 必须在内核命令行中使用 androidboot.veritymode 选项指定验证分区的真实模式/状态(现有要求)。 确保包含以下 AVB 修复程序:

提前装载设备树 (VBoot 2.0)

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

  • fsmgr_flagverify 变为 avb
  • 包含 AVB 元数据的所有分区都必须位于设备树的 vbmeta 条目中,即使相应的分区并非提前装载的分区(如 /boot)也是如此。

示例:N5X 上的 /system 和 /vendor

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

  • /system 使用 AVB 进行装载,且 /vendor 的装载不需要进行完整性验证。
  • 由于 Nexus 5X 没有 /vbmeta 分区,因此顶级 vbmeta 位于 /boot 分区的末端(有关详情,请参阅 AOSP 更改列表)。
    / {
      firmware {
        android {
          compatible = "android,firmware";
          vbmeta {
          compatible = "android,vbmeta";
          parts = "boot,system,vendor";
          };
      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,avb";
            };
        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。 请注意:

  • 很多分区都是在 vbmeta 条目中指定的,因为这些分区受 AVB 保护
  • 必须包含所有 AVB 分区,即使仅提前装载了 /vendor 也是如此。
  • 务必为 A/B 分区添加 slotselect
    / {
      vbmeta {
          compatible = "android,vbmeta";
      parts = "vbmeta,boot,system,vendor,dtbo";
      };
      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,avb";
            };
          };
        };
      };
    };
    

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

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

在 Android 7.x 及更早版本中,Android 不要求提供设备树支持,同时,没有针对供应商如何将 DT Blob 传递给内核或在何处存储这些 Blob 提供相关建议。不过,Android 8.0 建议提供此类支持,以将内核的板专属部分和仅限 SoC 访问的部分区分开来。

分区要求

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

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

  • 对于适用于板专属 DT 叠加层的每个内核映像,设备应该具有新的设备树 Blob 叠加层 (DTBO) 分区(要详细了解分区格式,请参阅 DTB/DTBO 分区)。 假设的前提是引导加载程序已经知道从何处以及如何加载 SoC 专用 DTB。
  • 应该针对 A/B 设备对叠加层 DT 分区进行 A/B 系统更新。对这些设备而言,恢复内核与 Android 内核相同,但分区必须进行 A/B 系统更新,因为这样才可以通过 OTA 进行更新。
  • 分区大小取决于板。
    • DT 叠加层分区大小取决于设备以及主 SoC 内核 DT Blob 上所需的更改量。
    • DTBO 分区的大小是构建 SoC 内核所需的更改数的函数。选择分区大小时需要为未来更新留出空间(通常,8MB 大小的分区已绰绰有余)。

引导加载程序要求

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

  • 引导加载程序应该知道如何以及从何处(考虑使用 A/B 设备的启动槽)以供应商独有的方式加载 SoC 专用 DT Blob。这通常是从内核映像的末端提取,因为 Blob 已附加到内核中。
  • 引导加载程序应该知道如何以及从何处(考虑使用 A/B 设备的启动槽)以供应商独有的方式加载叠加层 DT Blob。
  • 将组合设备树传递给内核之前,引导加载程序必须使用叠加层修补主 DT Blob。

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

核心内核要求

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

内核 .config 支持

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

CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y

内核版本

内核版本要求:

  • 2017 年构建的所有 SoC 都必须使用 4.4 版或更新版本的内核才能发布。
  • 在推出搭载 Android 8.0 的新 Android 设备时,所有其他 SoC 都必须使用 3.18 版或更新版本的内核。
  • 发布搭载 Android 8.0 的设备时,所有 SoC 仍然需要遵循启用 Treble 所需的内核更改要求,无论发布日期是什么时候。
  • 如有需要,发布时间早于 Android 8.0 但是将升级到 Android 8.0 的早期 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 的情况下也可以完成 VTS 测试。

Android 8.0 之后的版本

Android 8.0 建议任何板专属内核功能都采用可加载的内核模块和设备树叠加层的形式。对 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.0 及更高版本对应的设备内核。

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

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

上游化

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

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

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

  • Documentation/process(在 4.9 版及更早版本的内核中为 Documentation/development-process
  • Documentation/CodingStyle
  • Documentation/SubmittingPatches

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