可加载内核模块

作为 Android 8.0 中引入的模块内核要求的一部分,所有片上系统 (SoC) 内核都必须支持可加载内核模块。

内核配置选项

为了支持可加载内核模块,所有通用内核中的 android-base.config 都包含以下内核配置选项(或其内核版本等效项)

CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODVERSIONS=y

所有设备内核都必须启用这些选项。内核模块还应尽可能支持卸载和重新加载。

模块签名

GKI 供应商模块不支持模块签名。在需要支持已验证启动的设备上,Android 要求内核模块位于已启用 dm-verity 的分区中。这样就无需为各个模块的真实性进行签名。Android 13 引入了 GKI 模块的概念。GKI 模块使用内核的构建时签名基础架构来区分运行时 GKI 模块和其他模块。只要未签名模块仅使用允许列表上出现的符号或由其他未签名模块提供的符号,就允许加载这些模块。为了方便在 GKI 构建期间使用内核的构建时密钥对对 GKI 模块进行签名,GKI 内核配置已启用 CONFIG_MODULE_SIG_ALL=y。为了避免在设备内核构建期间对非 GKI 模块进行签名,您必须添加 # CONFIG_MODULE_SIG_ALL is not set 作为内核配置文件片段的一部分。

文件位置

虽然 Android 7.x 及更低版本不强制禁止内核模块(并包含对 insmodrmmod 的支持),但 Android 8.x 及更高版本建议在生态系统中使用内核模块。下表显示了三种 Android 启动模式下可能需要的特定于板级的外围设备支持。

启动模式 存储 显示 键盘 电池 电源管理IC (PMIC) 触摸屏 NFC、Wi-Fi、
蓝牙
传感器 相机
恢复模式
充电器模式
Android 系统

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

  • 所有内核都应具有内置的启动和挂载分区支持。
  • 内核模块必须从只读分区加载。
  • 对于需要进行验证启动的设备,内核模块应从已验证的分区加载。
  • 内核模块不应位于 /system 中。
  • 设备所需的 GKI 模块应从 /system/lib/modules 加载,这是一个指向 /system_dlkm/lib/modules 的符号链接。
  • 完整 Android 或充电器模式所需的 SoC 供应商的内核模块应位于 /vendor/lib/modules 中。
  • 如果存在 ODM 分区,则完整 Android 或充电器模式所需的 ODM 的内核模块应位于 /odm/lib/modules 中。否则,这些模块应位于 /vendor/lib/modules 中。
  • 恢复模式所需的 SoC 供应商和 ODM 的内核模块应位于恢复 ramfs 中的 /lib/modules 下。
  • 恢复模式和完整 Android 或充电器模式都需要的内核模块应同时存在于恢复 rootfs/vendor/odm 分区(如上所述)中。
  • 恢复模式中使用的内核模块不应依赖于仅位于 /vendor/odm 中的模块,因为这些分区在恢复模式下未挂载。
  • SoC 供应商内核模块不应依赖于 ODM 内核模块。

在 Android 7.x 及更低版本中,/vendor/odm 分区不会提前挂载。在 Android 8.x 及更高版本中,为了使从这些分区加载模块成为可能,已采取措施提前挂载分区,以用于 非 A/B 和 A/B 设备。这也确保了分区在 Android 和充电器模式下都已挂载。

Android 构建系统支持

BoardConfig.mk 中,Android 构建定义了一个 BOARD_VENDOR_KERNEL_MODULES 变量,该变量提供了 vendor 镜像的完整内核模块列表。此变量中列出的模块被复制到 vendor 镜像中的 /lib/modules/,并在 Android 中挂载后,出现在 /vendor/lib/modules 中(符合上述要求)。Vendor 内核模块的配置示例

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

在此示例中,vendor 内核模块预构建仓库被映射到上面列出的位置的 Android 构建中。

恢复镜像可能包含 vendor 模块的子集。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:vendor_modprobe:s0 -- /vendor/bin/modprobe -a -d \
        /vendor/lib/modules module_a module_b module_c ...

通常,内核模块必须使用与其要使用的内核一起编译(否则内核将拒绝加载该模块)。CONFIG_MODVERSIONS 通过检测应用程序二进制接口 (ABI) 中的中断来提供一种解决方法。此功能计算内核中每个导出的符号原型的循环冗余校验 (CRC) 值,并将这些值存储为内核的一部分;对于内核模块使用的符号,这些值也存储在内核模块中。加载模块时,会将模块使用的符号的值与内核中的值进行比较。如果这些值匹配,则加载模块;否则,加载失败。

要实现内核镜像与 vendor 镜像分开更新,请启用 CONFIG_MODVERSIONS。这样做允许对内核进行小的更新(例如来自 LTS 的错误修复),同时保持与 vendor 镜像中现有内核模块的兼容性。但是,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.rst 的文档。