想要实现 A/B 系统更新的原始设备制造商 (OEM) 和 SoC 供应商必须确保其引导加载程序实现 boot_control HAL,并将正确的参数传递到内核。
实现启动控件 HAL
支持 A/B 的引导加载程序必须在 hardware/libhardware/include/hardware/boot_control.h
中实现 boot_control
HAL。您可以使用 system/extras/bootctl
实用程序和 system/extras/tests/bootloader/
对实现进行测试。
此外,您还必须实现如下所示的状态机:

设置内核
要实现 A/B 系统更新,请执行以下操作:
- 择优挑选下列内核补丁程序系列(如果需要):
- 如果在没有使用 ramdisk 的情况下启动并使用“以恢复方式启动”,请择优挑选 android-review.googlesource.com/#/c/158491/ 中的代码变更。
- 要在没有使用 ramdisk 的情况下设置 dm-verity,请择优挑选 android-review.googlesource.com/#/q/status:merged+project:kernel/common+branch:android-3.18+topic:A_B_Changes_3.18 中的代码变更。
- 确保内核命令行参数包含中以下额外参数:
skip_initramfs rootwait ro init=/init root="/dev/dm-0 dm=system none ro,0 1 android-verity <public-key-id> <path-to-system-partition>"
<public-key-id>
值是用于验证 verity 表签名的公钥的 ID(如需了解详情,请参阅 dm-verity)。 - 将包含公钥的 .X509 证书添加到系统密钥环:
- 将
.der
格式的 .X509 证书复制到kernel
目录的根目录。如果 .X509 证书的格式为.pem
文件,请使用以下openssl
命令将.pem
格式转换为.der
格式:openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
- 构建
zImage
以将该证书添加为系统密钥环的一部分。要进行验证,请检查procfs
条目(需要启用KEYS_CONFIG_DEBUG_PROC_KEYS
):angler:/# cat /proc/keys 1c8a217e I------ 1 perm 1f010000 0 0 asymmetri Android: 7e4333f9bba00adfe0ede979e28ed1920492b40f: X509.RSA 0492b40f [] 2d454e3e I------ 1 perm 1f030000 0 0 keyring .system_keyring: 1/4
如果成功添加了 .X509 证书,则表示系统密钥环中存在相应公钥(突出显示的部分表示公钥 ID)。 - 将空格替换为
#
,并将其作为内核命令行中的<public-key-id>
传递。例如,传递Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f
代替<public-key-id>
。
- 将
设置构建变量
支持 A/B 更新的引导加载程序必须满足以下构建变量条件:
必须针对 A/B 更新目标定义的变量 |
/device/google/marlin/+/android-7.1.0_r1/device-common.mk 。
您可以选择执行编译中所述的安装后(但在重新启动前)dex2oat 步骤。 |
---|---|
强烈建议用于 A/B 目标 |
|
无法针对 A/B 目标定义的变量 |
|
(可选)针对调试 build 定义的变量 | PRODUCT_PACKAGES_DEBUG += update_engine_client |
设置分区(插槽)
A/B 设备不需要恢复分区或缓存分区,因为 Android 已不再使用这些分区。数据分区现在用于存储下载的 OTA 更新包,而恢复映像代码位于启动分区。
所有 A/B 分区的分区都应按如下方式命名(槽位的名称始终为 a
、b
等): boot_a
,
boot_b
, system_a
, system_b
,
vendor_a
, vendor_b
.
缓存
对于非 A/B 更新,缓存分区用于存储下载的 OTA 更新包,并在执行更新时暂时隐藏区块。确定缓存分区的大小一直是一个棘手的问题,所需的大小取决于您想要执行的更新。最糟糕的情况是缓存分区与系统映像一样大。如果采用 A/B 更新,则无需隐藏区块(因为您始终是向当前未使用的分区写入数据);如果流式传输 A/B 更新,则无需在执行更新之前下载整个 OTA 更新包。
恢复
恢复 RAM 磁盘现已包含在 boot.img
文件中。进入恢复模式时,引导加载程序无法在内核命令行中添加 skip_initramfs
选项。
对于非 A/B 更新,恢复分区包含用于执行更新的代码。A/B 更新由在正常启动的系统映像中运行的 update_engine
执行。同时,仍有一种用于实现恢复出厂设置和旁加载更新包的恢复模式(“恢复”就由此而来)。恢复模式的代码和数据存储在 ramdisk 的常规启动分区中;为启动进入系统映像,引导加载程序会指示内核跳过 ramdisk(否则,设备会启动进入恢复模式)。恢复模式很小(其中大部分已在启动分区上),所以启动分区的大小不会增加。
Fstab
slotselect
参数必须位于 A/B 更新分区所对应的行中。例如:
<path-to-block-device>/vendor /vendor ext4 ro wait,verify=<path-to-block-device>/metadata,slotselect
不得将任何分区命名为 vendor
。系统会选择分区 vendor_a
或 vendor_b
,并将其装载到 /vendor
装载点。
内核插槽参数
应通过特定的设备树 (DT) 节点 (/firmware/android/slot_suffix
) 或 androidboot.slot_suffix
内核命令行或 bootconfig 参数传递当前插槽后缀。
默认情况下,fastboot 会刷写 A/B 设备上的当前插槽。如果更新包还包含其他非当前插槽的映像,则 fastboot 也会刷写这些映像。可用选项包括:
--slot SLOT
:替换默认行为,并提示 fastboot 刷写以参数形式传入的插槽。--set-active [SLOT]
:将插槽设为活动插槽。如果未指定可选参数,则将当前插槽设为活动插槽。fastboot --help
:获取有关命令的详细信息。
如果引导加载程序实现 fastboot,则应该支持命令 set_active <slot>
,该命令将当前活动插槽设置为指定插槽(此外,还必须清除该插槽的不可启动标记并重置重试计数)默认值)。引导加载程序还应支持以下变量:
has-slot:<partition-base-name-without-suffix>
:如果指定分区支持插槽,则返回“yes”,否则返回“no”。current-slot
:返回接下来将从中启动的插槽后缀。slot-count
:返回一个表示可用插槽数量的整数。目前支持两个插槽,因此该值为2
。slot-successful:<slot-suffix>
:如果指定插槽已标记为成功启动,则返回“yes”,否则返回“no”。slot-unbootable:<slot-suffix>
:如果指定插槽被标记为不可启动,则返回“yes”,否则返回“no”。slot-retry-count
:启动指定插槽的剩余重试次数。
要查看所有变量,请运行 fastboot getvar all
。
生成 OTA 更新包
OTA 更新包工具遵循的命令与不采取 A/B 更新的设备相同。target_files.zip
文件必须通过为 A/B 目标定义构建变量生成。OTA 更新包工具会自动识别并生成格式适用于 A/B 更新程序的更新包。
例如:
- 生成完整 OTA:
./build/make/tools/releasetools/ota_from_target_files \ dist_output/tardis-target_files.zip \ ota_update.zip
- 生成增量 OTA:
./build/make/tools/releasetools/ota_from_target_files \ -i PREVIOUS-tardis-target_files.zip \ dist_output/tardis-target_files.zip \ incremental_ota_update.zip
配置分区
update_engine
可以更新同一磁盘中定义的任何一对 A/B 分区。一对分区有一个公共前缀(例如 system
或 boot
)和按插槽划分的后缀(例如 _a
)。载荷生成器为其定义更新的分区列表由 AB_OTA_PARTITIONS
make 变量配置。
例如,如果磁盘中有一对分区 bootloader_a
和 booloader_b
(_a
和 _b
是槽后缀),您可以通过指定以下内容来更新这些分区政策:
AB_OTA_PARTITIONS := \ boot \ system \ bootloader
由 update_engine
更新的所有分区不得由系统的其余部分修改。在增量更新期间,来自当前插槽的二进制数据将用于在新插槽中生成数据。任何修改都可能导致新插槽数据在更新过程中无法通过验证,从而导致更新失败。
配置安装后步骤
对于每个已更新的分区,您可以使用一组键值对配置不同的安装后步骤。要在新映像中运行位于 /system/usr/bin/postinst
的程序,请指定相对于系统分区中文件系统根目录的路径。
例如,usr/bin/postinst
为 system/usr/bin/postinst
(如果未使用 RAM 磁盘)。此外,请指定要传递到 mount(2)
系统调用的文件系统类型。
请将以下代码添加到产品或设备的 .mk
.mk 文件中(如果适用):
AB_OTA_POSTINSTALL_CONFIG += \ RUN_POSTINSTALL_system=true \ POSTINSTALL_PATH_system=usr/bin/postinst \ FILESYSTEM_TYPE_system=ext4
编译
出于安全考虑,system_server
无法使用即时 (JIT) 编译。这意味着,您必须至少为 system_server
system_server 及其依赖项提前编译 odex 文件;其他内容则可以先不编译。
要在后台编译应用,您必须将以下内容添加到产品的设备配置(位于产品的 device.mk 中):
- 向 build 中添加原生组件,以确保编译脚本和二进制文件能够顺利编译并添加到系统映像中。
# A/B OTA dexopt package PRODUCT_PACKAGES += otapreopt_script
- 将编译脚本连接到
update_engine
,以便作为安装后步骤运行。# A/B OTA dexopt update_engine hookup AB_OTA_POSTINSTALL_CONFIG += \ RUN_POSTINSTALL_system=true \ POSTINSTALL_PATH_system=system/bin/otapreopt_script \ FILESYSTEM_TYPE_system=ext4 \ POSTINSTALL_OPTIONAL_system=true
如需了解如何将预先优化的文件安装到未使用的第二个系统分区,请参阅 DEX_PREOPT 文件的首次启动安装。