实施 A/B 更新

希望实施 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/ 测试实现。

您还必须实施下图所示的状态机

图 1. 引导加载程序状态机

设置内核

要实施 A/B 系统更新

  1. 挑选以下内核补丁系列(如果需要)
  2. 确保内核命令行参数包含以下额外参数
    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)。
  3. 将包含公钥的 .X509 证书添加到系统密钥环
    1. 将以 .der 格式格式化的 .X509 证书复制到 kernel 目录的根目录。如果 .X509 证书的格式为 .pem 文件,请使用以下 openssl 命令从 .pem 格式转换为 .der 格式
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. 构建 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)。
    3. 将空格替换为 #,并在内核命令行中将其作为 <public-key-id> 传递。例如,传递 Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f 而不是 <public-key-id>

设置构建变量

支持 A/B 的引导加载程序必须满足以下构建变量条件

必须为 A/B 目标定义
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    以及通过 update_engine 更新的其他分区(radio、bootloader 等)
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
有关示例,请参阅 /device/google/marlin/+/android-7.1.0_r1/device-common.mk。您可以选择执行编译中描述的安装后(但在重新启动前)dex2oat 步骤。
强烈建议用于 A/B 目标
  • 定义 TARGET_NO_RECOVERY := true
  • 定义 BOARD_USES_RECOVERY_AS_BOOT := true
  • 不要定义 BOARD_RECOVERYIMAGE_PARTITION_SIZE
不能为 A/B 目标定义
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
可选用于调试版本 PRODUCT_PACKAGES_DEBUG += update_engine_client

设置分区(槽位)

A/B 设备不需要恢复分区或缓存分区,因为 Android 不再使用这些分区。数据分区现在用于下载的 OTA 软件包,恢复映像代码位于启动分区上。所有 A/B 分区都应按如下方式命名(槽位始终命名为 ab 等):boot_aboot_bsystem_asystem_bvendor_avendor_b

缓存

对于非 A/B 更新,缓存分区用于存储下载的 OTA 包,并在应用更新时临时存放块。一直以来都没有一个好的方法来确定缓存分区的大小:它需要多大取决于你想应用的更新。最坏的情况是缓存分区和系统镜像一样大。使用 A/B 更新,则无需存放块(因为你始终写入到当前未使用的分区),而使用流式 A/B,则无需在应用更新之前下载整个 OTA 包。

Recovery

Recovery RAM 磁盘现在包含在 boot.img 文件中。进入 recovery 模式时,引导加载程序不能在内核命令行中添加 skip_initramfs 选项。

对于非 A/B 更新,recovery 分区包含用于应用更新的代码。A/B 更新由在常规启动的系统镜像中运行的 update_engine 应用。仍然有一个 recovery 模式用于实现恢复出厂设置和侧载更新包(这也是名称 "recovery" 的由来)。recovery 模式的代码和数据存储在常规启动分区中的 ramdisk 中;要启动到系统镜像,引导加载程序会告知内核跳过 ramdisk(否则设备会启动进入 recovery 模式)。Recovery 模式很小(并且其中大部分已在启动分区上),因此启动分区的大小不会增加。

Fstab

slotselect 参数必须在 A/B 分区的行中。例如

<path-to-block-device>/vendor  /vendor  ext4  ro
wait,verify=<path-to-block-device>/metadata,slotselect

任何分区都不应命名为 vendor。而是会选择分区 vendor_avendor_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:<slot-suffix>。尝试启动给定槽的剩余重试次数。

要查看所有变量,请运行 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 分区。一对分区具有共同的前缀(例如 systemboot)和每个槽的后缀(例如 _a)。payload 生成器为其定义更新的分区列表由 AB_OTA_PARTITIONS make 变量配置。

例如,如果包含一对分区 bootloader_abooloader_b_a_b 是槽后缀),则可以通过在产品或板配置上指定以下内容来更新这些分区

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

update_engine 更新的所有分区都不得被系统的其余部分修改。在增量或增量更新期间,当前槽中的二进制数据用于生成新槽中的数据。任何修改都可能导致新槽数据在更新过程中无法通过验证,从而导致更新失败。

配置安装后步骤

你可以使用一组键值对为每个更新的分区配置不同的安装后步骤。要在新镜像中运行位于 /system/usr/bin/postinst 的程序,请指定相对于系统分区文件系统根目录的路径。

例如,usr/bin/postinstsystem/usr/bin/postinst(如果未使用 RAM 磁盘)。此外,指定要传递给 mount(2) 系统调用的文件系统类型。将以下内容添加到产品或设备 .mk 文件中(如果适用)

AB_OTA_POSTINSTALL_CONFIG += \
  RUN_POSTINSTALL_system=true \
  POSTINSTALL_PATH_system=usr/bin/postinst \
  FILESYSTEM_TYPE_system=ext4

编译应用

应用可以在使用新系统镜像重启之前在后台编译。要在后台编译应用,请将以下内容添加到产品的设备配置中(在产品的 device.mk 中)

  1. 在构建中包含原生组件,以确保编译脚本和二进制文件已编译并包含在系统镜像中。
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. 将编译脚本连接到 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
    

有关在未使用的第二个系统分区中安装 preopted 文件的帮助,请参阅 DEX_PREOPT 文件的首次启动安装