在 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 及更早版本对内核模块(包括对 insmod
和 rmmod
的支持)没有强制要求,而 Android 8.x 及更高版本建议在生态系统中使用内核模块。下表显示了 Android 的 3 种启动模式所需的潜在板专用外设支持:
启动模式 | 存储 | 显示 | 拨号键盘 | 电池 | PMIC | 触摸屏 | NFC、WLAN、 蓝牙 |
传感器 | 相机 |
---|---|---|---|---|---|---|---|---|---|
恢复 | |||||||||
充电 | |||||||||
Android |
除了按 Android 启动模式的可用情况对内核模块进行分类之外,还可以按照所有者(SoC 供应商或 ODM)进行分类。如果使用了内核模块,则它们在文件系统中的放置位置的要求如下:
/system
中。/vendor/lib/modules
中。/odm/lib/modules
中。如果不存在,则这些模块应该位于 /vendor/lib/modules
中。/lib/modules
下的恢复 ramfs
中。rootfs
和 /vendor
或 /odm
分区中(如上所述)。/vendor
或 /odm
中的模块,因为这些分区在恢复模式下没有装载。在 Android 7.x 及更早版本中,/vendor
和 /odm
分区不会提前装载。在 Android 8.x 及更高版本中,为使模块能够从这些分区加载,已进行相关配置,以便为非 A/B 和 A/B 设备提前装载分区。这还确保了在 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 -a
从 init.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_SCHEDSTATS
或 CONFIG_TASK_DELAY_ACCT
时发生)时才显示。如果这些配置选项的状态发生变化,task_struct
结构的布局也将发生变化,同时,从内核导出的使用 task_struct
的所有接口都会发生变化(如 kernel/sched/core.c
中的 set_cpus_allowed_ptr
)。与使用这些接口的之前编译的内核模块的兼容性将会被破坏,这就需要使用新的内核配置重新编译这些模块。
要详细了解 CONFIG_MODVERSIONS
,请参阅位于 Documentation/kbuild/modules.txt
的内核树中的相关文档。
所有支持 Treble 的设备都必须启用第一阶段装载,以确保 init
可以加载分布在 system
和 vendor
分区的 SELinux 政策片段(这样还可实现在内核启动后尽快加载内核模块)。
Android 必须有权访问模块所在的文件系统。为此,Android 8.x 及更高版本支持在 init
的第一阶段(即初始化 SELinux 之前)装载 /system
、/vendor
或 /odm
。设备制造商可以使用设备树叠加层为提前装载的分区指定 fstab
条目。
使用 VBoot 1.0 提前装载分区的要求包括:
fstab
和设备树条目中使用其 by-name 符号链接。例如,确保对分区进行命名且设备节点为 /dev/block/…./by-name/{system,vendor,odm}
,而不是使用 /dev/block/mmcblk0pX
指定分区。
device/oem/project/device.mk
中)为 PRODUCT_{SYSTEM,VENDOR}_VERITY_PARTITION
和 CUSTOM_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
fstab
文件片段中出现重复。例如,指定某个条目以在设备树中装载 /vendor
时,fstab
文件不得重复该条目。verifyatboot
提前装载需要 verifyatboot 的分区(此操作不受支持)。androidboot.veritymode
选项指定验证分区的真实模式/状态(现有要求)。在 Android 8.x 及更高版本中,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
文件中一样)。
slotselect fs_mgr
选项。verify fs_mgr
选项。
下面的示例显示的是在 Nexus 6P 上为 system
和 vendor
分区提前装载设备树:
/ { 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
提前装载设备树(请务必为 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 是 Android 验证启动 (AVB)。 使用 VBoot 2.0 提前装载分区的要求如下:
fstab
和设备树条目中使用其 by-name 符号链接。例如,确保对分区进行命名且设备节点为 /dev/block/…./by-name/{system,vendor,odm}
,而不是使用 /dev/block/mmcblk0pX
指定分区。PRODUCT_{SYSTEM,VENDOR}_VERITY_PARTITION
和 CUSTOM_IMAGE_VERITY_BLOCK_DEVICE
)对 VBoot 2.0 而言并非必需的。相反,您应该定义 VBoot 2.0 中引入的新编译系统变量(包括 BOARD_AVB_ENABLE := true
);有关完整配置,请参阅适用于 AVB 的编译系统集成。fstab
文件片段中出现重复。例如,如果您指定某个条目以在设备树中装载 /vendor
,则 fstab
文件不得重复该条目。verifyatboot
,无论是否启用了提前装载。androidboot.veritymode
选项指定验证分区的真实模式/状态(现有要求)。
确保包含以下 AVB 修复程序:
VBoot 2.0 设备树中的配置与 VBoot 1.0 中的大致相同,但还有以下几项不同之处:
fsmgr_flag
由 verify
变为 avb
。/boot
)也是如此。下面的示例显示的是在 Nexus 5X 上为 system
和 vendor
分区提前装载设备树。请注意:
/system
使用 AVB 进行装载,且 /vendor
的装载不需要进行完整性验证。/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
。
请注意:
/vendor
也是如此。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 对 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
读取内核配置的选项。
所有设备内核都必须完整启用 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
的情况下也可以完成 VTS 测试。
当前的 Android 版本建议以设备中的内核模块的形式编译和推出所有板专属代码。对 Android 而言,内核的其余部分为一个整体(无论它实际上是单片内核,还是其中的一部分是作为内核模块编译的)。
该单片内核是可以在 SoC 供应商的参考硬件上启动的 SoC 内核,但仅限于此。如今,对 SoC 内核的处理方式与通用内核类似;SoC 内核在板专属的代码库中会有大量副本。这种分发模型会导致,针对每个分支中的同一错误,系统会采取极为不同的方式修复 SoC 内核;这样一来,由于会在不同的时间择优挑选或修复同一错误的方式不同,未来的内核更新会有延迟。要解决此问题,必须单独提供 SoC 内核,以便使用 SoC 的每个人都可以为同一 SoC 内核做贡献。
图 1(下图)是一个常见示例,显示了 SoC 内核如何随着时间的推移在各个 Android 版本以及 ODM 之间逐渐碎片化。
图 1 表明:
针对通用 SoC 内核提议的模型可解决上行合并更改(如 SoC 专属错误修复程序、LTS 升级、安全漏洞修复程序等)导致的问题。 图 2(下图)显示了工作流程在按 SoC 和内核进行过统一的理想场景中如何变化:
这种方法旨在通过推荐并与设备制造商展开协作以采用最新的通用 SoC 内核,解决内核代码库碎片化的问题。Android 8.x 及更高版本为 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
社区会稍作审核就接受独立的驱动程序和文件系统,并将其纳入内核的暂存区,然后在其中努力提高代码质量。