如需在新设备上实现虚拟 A/B 或者对已发布设备进行改造,您必须更改设备专属代码。
构建标志
使用虚拟 A/B 的设备必须配置为 A/B 设备,并且必须搭载动态分区。
对于搭载虚拟 A/B 的设备,请将它们设置为继承虚拟 A/B 设备的基本配置:
$(call inherit-product, \
$(SRC_TARGET_DIR)/product/virtual_ab_ota.mk)
发布时搭载虚拟 A/B 的设备只需要 BOARD_SUPER_PARTITION_SIZE
一半的板大小,因为 B 槽位不再位于 super 分区中。也就是说,BOARD_SUPER_PARTITION_SIZE
必须大于或等于“各个更新组大小的总和 + 开销”,而后又必须大于或等于“各个分区大小的总和 + 开销”。
对于 Android 13 及更高版本,如需使用虚拟 A/B 启用压缩快照,请继承以下基本配置:
$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
$(call inherit-product, \
$(SRC_TARGET_DIR)/product/virtual_ab_ota/android_t_baseline.mk)
此配置会使用虚拟 A/B 启用用户空间快照,同时使用免运维压缩方法。然后,您可以将压缩方法配置为一种受支持的方法,即 gz
或 brotli
。
PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD := gz
对于 Android 12,如需使用虚拟 A/B 启用压缩快照,请继承以下基本配置:
$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
$(call inherit-product, \
$(SRC_TARGET_DIR)/product/virtual_ab_ota/compression.mk)
XOR 压缩
对于升级到 Android 13 及更高版本的设备,XOR 压缩功能默认处于停用状态。如需启用 XOR 压缩,请将下面这行代码添加到设备的 .mk
文件中。
PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled=true
对于从 android_t_baseline.mk
继承的设备,XOR 压缩功能默认处于启用状态。
用户空间合并
对于升级到 Android 13 及更高版本的设备,设备映射器分层中所述的用户空间合并流程默认处于停用状态。如需启用用户空间合并,请将下面这行代码添加到设备的 .mk
文件中:
PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true
在发布时搭载 Android 13 及更高版本的设备上,用户空间合并默认处于启用状态。
启动控件 HAL
启动控件 HAL 为 OTA 客户端提供了一个用于控制启动槽位的接口。虚拟 A/B 需要对启动控件 HAL 进行 Minor 版本升级,因为需要额外的 API 来确保引导加载程序在刷写/恢复出厂设置期间受到保护。如需查看最新版本 HAL 定义,请参阅 IBootControl.hal 和 types.hal。
// hardware/interfaces/boot/1.1/types.hal
enum MergeStatus : uint8_t {
NONE, UNKNOWN, SNAPSHOTTED, MERGING, CANCELLED };
// hardware/interfaces/boot/1.1/IBootControl.hal
package android.hardware.boot@1.1;
interface IBootControl extends @1.0::IBootControl {
setSnapshotMergeStatus(MergeStatus status)
generates (bool success);
getSnapshotMergeStatus()
generates (MergeStatus status);
}
// Recommended implementation
Return<bool> BootControl::setSnapshotMergeStatus(MergeStatus v) {
// Write value to persistent storage
// e.g. misc partition (using libbootloader_message)
// bootloader rejects wipe when status is SNAPSHOTTED
// or MERGING
}
fstab 的变更
metadata 分区的完整性对于启动流程至关重要,特别是在刚应用 OTA 更新后。因此,必须在 first_stage_init
装载 metadata 分区之前检查 metadata 分区。为此,请将 check
fs_mgr 标志添加到 /metadata
的条目中。示例如下:
/dev/block/by-name/metadata /metadata ext4 noatime,nosuid,nodev,discard,sync wait,formattable,first_stage_mount,check
内核要求
如需启用快照功能,请将 CONFIG_DM_SNAPSHOT
设置为 true
。
对于使用 F2FS 的设备,请添加 f2fs: export FS_NOCOW_FL flag to user 内核补丁程序,以修复文件固定问题。此外,也请添加 f2fs: support aligned pinned file 内核补丁程序。
虚拟 A/B 依赖于内核版本 4.3 中添加的功能:snapshot
和 snapshot-merge
目标中的 overflow 状态位。所有搭载 Android 9 及更高版本的设备应该都已有内核版本 4.4 或更高版本。
如需启用压缩快照,支持的最低内核版本为 4.19。设置 CONFIG_DM_USER=m
或 CONFIG_DM_USER=y
。如果使用前者(模块),则模块必须在第一阶段 ramdisk 中加载。这可以通过在设备 makefile 中添加以下行来实现:
BOARD_GENERIC_RAMDISK_KERNEL_MODULES_LOAD := dm-user.ko
针对升级到 Android 11 的设备进行改造
升级到 Android 11 后,搭载动态分区的设备可以选择性地改造虚拟 A/B。更新流程与改造发布时搭载虚拟 A/B 的设备大致相同,但存在一些细微差别:
COW 文件的位置 - 对于新发布设备,OTA 客户端会使用 super 分区中所有可用的空白空间,然后再使用
/data
中的空间。对于改造设备,super 分区中始终会有足够的空间,因此绝不会在/data
中创建 COW 文件。构建时功能标志 - 对于改造了虚拟 A/B 的设备,
PRODUCT_VIRTUAL_AB_OTA
和PRODUCT_VIRTUAL_AB_OTA_RETROFIT
均设置为true
,如下所示:(call inherit-product, \
(SRC_TARGET_DIR)/product/virtual_ab_ota_retrofit.mk)
super 分区大小 - 发布时搭载虚拟 A/B 的设备可以将
BOARD_SUPER_PARTITION_SIZE
减半,因为 B 槽位并非位于 super 分区中。改造了虚拟 A/B 的设备会保持旧的 super 分区大小不变,因此BOARD_SUPER_PARTITION_SIZE
大于或等于“2 * 各个更新组大小的总和 + 开销”,而后又大于或等于“2 * 分区大小的总和 + 开销”。
引导加载程序的变更
在更新的合并步骤中,/data
保存着 Android 操作系统的唯一完整实例。迁移开始后,原生 system
、vendor
和 product
分区在复制完成后才会变得完整。如果该设备在此过程中恢复出厂设置(无论是通过恢复功能还是“系统设置”对话框),它将不可启动。
在清空 /data
之前,请根据设备状态完成恢复或回滚中的合并操作:
- 如果之前成功启动了这个新的 build,请完成迁移。
- 否则,请回滚到原来的槽位:
- 对于动态分区,请回滚到先前的状态。
- 对于静态分区,请将活动槽位设置为旧槽位。
如果设备处于解锁状态,引导加载程序和 fastbootd
都可以清空 /data
分区。虽然 fastbootd
可以强制迁移完成,但引导加载程序不能。引导加载程序不知道合并是否正在进行,也不知道 /data
中的哪些块构成了操作系统分区。设备必须通过执行以下操作来防止用户在不知不觉中使设备无法运行(死机):
- 实现启动控件 HAL,以便引导加载程序可以读取
setSnapshotMergeStatus()
方法设置的值。 - 如果合并状态为
MERGING
,或者合并状态为SNAPSHOTTED
且槽位已更改为刚刚更新过的槽位,系统就必须在引导加载程序中拒绝擦除以下分区的请求:userdata
、metadata
或存储合并状态的分区。 - 实现
fastboot snapshot-update cancel
命令,这样一来,用户可以向引导加载程序发送信号,表示希望绕过此保护机制。 - 修改自定义刷写工具或脚本,在刷写整个设备时发出
fastboot snapshot-update cancel
。这个命令可以安全发出,因为刷写整个设备会移除 OTA。工具可以通过实现fastboot getvar snapshot-update-status
在运行时检测此命令。此命令有助于区分错误条件。
示例
struct VirtualAbState {
uint8_t StructVersion;
uint8_t MergeStatus;
uint8_t SourceSlot;
};
bool ShouldPreventUserdataWipe() {
VirtualAbState state;
if (!ReadVirtualAbState(&state)) ...
return state.MergeStatus == MergeStatus::MERGING ||
(state.MergeStatus == MergeStatus::SNAPSHOTTED &&
state.SourceSlot != CurrentSlot()));
}
Fastboot 工具的变更
Android 11 对 fastboot 协议做出了以下变更:
getvar snapshot-update-status
- 用于返回启动控件 HAL 传达给引导加载程序的值:- 如果状态为
MERGING
,引导加载程序必须返回merging
。 - 如果状态为
SNAPSHOTTED
,引导加载程序必须返回snapshotted
。 - 否则,引导加载程序必须返回
none
。
- 如果状态为
snapshot-update merge
- 用于完成合并操作,并在必要时启动到恢复/fastbootd 映像。此命令仅在snapshot-update-status
为merging
时有效,且仅在 fastbootd 中受支持。snapshot-update cancel
- 用于将启动控件 HAL 的合并状态设置为CANCELLED
。此命令在设备处于锁定状态时无效。erase
或wipe
- 在对metadata
、userdata
或者保存引导控件 HAL 的合并状态的分区执行erase
或wipe
命令之前,应检查快照合并状态。如果状态为MERGING
或SNAPSHOTTED
,设备应中止操作。set_active
- 在执行更改活动槽位的set_active
命令之前,应检查快照合并状态。如果状态为MERGING
,设备应中止操作。如果相应状态为SNAPSHOTTED
,可以安全地更改槽位。
上述变更旨在防止意外地使设备不可启动,但它们可能会对自动化工具造成破坏。如果命令用作刷写所有分区的组件(例如运行 fastboot flashall
),建议使用以下流程:
- 查询
getvar snapshot-update-status
。 - 如果快照更新状态为
merging
或snapshotted
,请发出snapshot-update cancel
命令。 - 继续执行刷写步骤。
降低存储空间要求
对于没有在 super 中分配完整的 A/B 存储空间且预计会在必要时使用 /data
的设备,强烈建议使用块映射工具。块映射工具可使 build 之间的块分配保持一致,从而减少对快照的非必要写入。如需了解相关内容,请参阅缩减 OTA 大小。
OTA 压缩方法
可以针对不同的性能指标调整 OTA 软件包。Android 目前提供了几种受支持的压缩方法(gz
、lz4
和 none
),这些方法在安装时间、Cow 使用的空间、启动时间和快照合并时间之间存在一些权衡取舍。具有压缩功能的虚拟 AB 的默认启用选项是 gz compression method
。(注意:压缩方法之间的相对性能因 CPU 速度和存储吞吐量而异,而这些可能因设备而异)。在下方生成的所有 OTA 软件包都停用了 PostInstall,这会略微减慢启动速度。未压缩的整个 OTA 的动态分区总大小为 4.81GB)
1. Pixel 6 Pro 上的增量 OTA
安装时间(无安装后阶段) | Cow 使用的空间 | OTA 后启动时间 | 快照合并时间 | |
---|---|---|---|---|
gz | 24 分钟 | 1.18 GB | 40.2 秒 | 45.5 秒 |
lz4 | 13 分钟 | 1.49 GB | 37.4 秒 | 37.1 秒 |
无 | 13 分钟 | 2.90 GB | 37.6 秒 | 40.7 秒 |
2. Pixel 6 Pro 上的完整 OTA
安装时间(无安装后阶段) | Cow 使用的空间 | OTA 后启动时间 | 快照合并时间 | |
---|---|---|---|---|
gz | 23分钟 | 2.79 GB | 24.9 秒 | 41.7 秒 |
lz4 | 12 分钟 | 3.46 GB | 20.0 秒 | 25.3 秒 |
无 | 10 分钟 | 4.85 GB | 20.6 秒 | 29.8 秒 |