Virtual A/B 是 Android 的主要更新机制。Virtual A/B 基于旧版 A/B 更新(请参阅A/B 系统更新)和非 A/B 更新(在版本 15 中已弃用)构建而成,旨在减少更新的空间开销。
Virtual A/B 实际上没有用于动态分区的额外槽位,请参阅动态分区。而是将增量写入快照,然后在确认启动成功后合并到基本分区中。Virtual A/B 使用 Android 特定的快照格式。请参阅用于压缩快照的 COW 格式,该格式允许压缩快照并最大限度地减少磁盘空间使用量。在完整 OTA 中,通过压缩可将快照大小减少约 45%,增量 OTA 快照大小则减少约 55%。
Android 12 提供了 Virtual A/B 压缩选项,用于压缩快照分区。Virtual A/B 提供以下功能
- Virtual A/B 更新与 A/B 更新一样都是无缝的(更新完全在设备运行时在后台进行)。Virtual A/B 更新最大限度地缩短了设备离线和无法使用的时间。
- Virtual A/B 更新可以回滚。如果新的操作系统无法启动,设备会自动回滚到之前的版本。
- Virtual A/B 更新通过仅复制启动加载程序使用的分区来使用最少的额外空间。其他可更新分区均已快照。
背景和术语
本部分定义了术语并描述了支持 Virtual A/B 的技术。在 OTA 安装期间,新的操作系统数据会写入其物理分区的新槽位,或写入 Android 特定的 COW 设备。设备重启后,动态分区数据将通过使用 dm-user 和 snapuserd 守护程序合并回其基本设备。此过程完全在用户空间中进行。
Device-mapper
Device-mapper 是 Android 中常用的 Linux 虚拟块层。借助动态分区,/system
等分区是分层设备的堆栈
- 堆栈的底部是物理超级分区(例如,
/dev/block/by-name/super
)。 - 中间是一个
dm-linear
设备,用于指定超级分区中的哪些块构成给定的动态分区。在 A/B 设备上,这显示为/dev/block/mapper/system_[a|b]
,在非 A/B 设备上,这显示为/dev/block/mapper/system
。 - 顶层存在一个
dm-verity
设备,专为验证分区而创建。此设备验证dm-linear
设备上的块是否已正确签名。它显示为/dev/block/mapper/system-verity
,并且是/system
挂载点的来源。
图 1 显示了 /system
挂载点下的堆栈是什么样的。
图 1. /system 挂载点下的堆栈
压缩快照
在 Android 12 及更高版本中,由于 /data
分区上的空间需求可能很高,因此您可以在构建中启用压缩快照,以解决 /data
分区更高的空间需求。
Virtual A/B 压缩快照构建于以下 Android 12 及更高版本中提供的组件之上
这些组件实现了压缩。为实现压缩快照功能而做的其他必要更改在接下来的章节中给出:压缩快照的 COW 格式、dm-user 和 snapuserd。
压缩快照的 COW 格式
在 Android 12 及更高版本中,压缩快照使用 Android 特定的 COW 格式。COW 格式包含有关 OTA 的元数据,并具有包含 COW 操作和新操作系统数据的不同缓冲区。与仅允许替换操作的内核快照格式相比(将基本映像中的块X替换为快照中块Y的内容),Android 压缩快照 COW 格式更具表现力,并支持以下操作
- 复制:基本设备中的块X应替换为基本设备中的块Y。
- 替换:基本设备中的块X应替换为快照中块Y的内容。这些块都经过 gz 压缩。
- 零:基本设备中的块X应替换为全零。
- XOR:COW 设备存储块X和块Y之间 XOR 压缩的字节。(在 Android 13 及更高版本中可用。)
完整 OTA 更新仅包含替换和零操作。增量 OTA 更新还可以包含复制操作。
磁盘上的完整快照布局如下所示
图 2. 磁盘上的 Android COW 格式
dm-user
dm-user 内核模块使 userspace
能够实现设备映射器块设备。dm-user 表条目在 /dev/dm-user/<control-name>
下创建一个杂项设备。userspace
进程可以轮询该设备以接收来自内核的读取和写入请求。每个请求都有一个关联的缓冲区,供用户空间填充(对于读取)或传播(对于写入)。
dm-user
内核模块为内核提供了一个新的用户可见接口,该接口不属于上游 kernel.org 代码库。在它成为上游代码库之前,Google 保留在 Android 中修改 dm-user
接口的权利。
snapuserd
dm-user
的 snapuserd
用户空间组件实现了 Virtual A/B 压缩。Snapuserd 是一个用户空间守护进程,负责写入和读取 Android COW 设备。所有到快照的 I/O 都必须通过此服务。在 OTA 安装期间,新的操作系统数据由 snapuserd(使用压缩)写入快照。元数据的解析和新块数据的解包也在此处处理。
XOR 压缩
对于搭载 Android 13 及更高版本的设备,默认启用的 XOR 压缩功能使用户空间快照能够存储旧块和新块之间 XOR 压缩的字节。当 Virtual A/B 更新中仅更改块中的少量字节时,XOR 压缩存储方案使用的空间比默认存储方案少,因为快照不存储完整的 4K 字节。快照大小的减小是可能的,因为 XOR 数据包含许多零,并且比原始块数据更容易压缩。在 Pixel 设备上,XOR 压缩将快照大小减少 25% 到 40%。
对于升级到 Android 13 及更高版本的设备,必须启用 XOR 压缩。有关详细信息,请参阅 XOR 压缩。
快照合并
对于搭载 Android 13 及更高版本的设备,Virtual A/B 压缩中的快照和快照合并过程由 snapuserd
用户空间组件执行。对于升级到 Android 13 及更高版本的设备,必须启用此功能。有关详细信息,请参阅 用户空间合并。
以下描述了 Virtual A/B 压缩过程
- 框架从
dm-verity
设备挂载/system
分区,该设备堆叠在dm-user
设备之上。这意味着来自根文件系统的每个 I/O 都路由到dm-user
。 dm-user
将 I/O 路由到用户空间snapuserd
守护进程,该守护进程处理 I/O 请求。- 当合并操作完成时,框架将
dm-verity
折叠到dm-linear
(system_base
) 之上,并删除dm-user
。
图 3. Virtual A/B 压缩过程
快照合并过程可能会中断。如果在合并过程中重新启动设备,则合并过程将在重新启动后恢复。
Init 转换
当使用压缩快照启动时,第一阶段 init 必须启动 snapuserd
以挂载分区。这带来了一个问题:当加载和强制执行 sepolicy
时,snapuserd
被置于错误的上下文中,并且其读取请求失败,并出现 selinux 拒绝。
为了解决这个问题,snapuserd
与 init
同步转换,如下所示
- 第一阶段
init
从 ramdisk 启动snapuserd
,并将指向它的打开文件描述符保存在环境变量中。 - 第一阶段
init
将根文件系统切换到系统分区,然后执行init
的系统副本。 init
的系统副本将组合的 sepolicy 读取到字符串中。Init
在所有 ext4 支持的页面上调用mlock()
。然后,它停用快照设备的所有设备映射器表,并停止snapuserd
。在此之后,禁止从分区读取数据,因为这样做会导致死锁。- 使用指向
snapuserd
的 ramdisk 副本的打开描述符,init
使用正确的 selinux 上下文重新启动守护进程。快照设备的设备映射器表被重新激活。 - Init 调用
munlockall()
- 再次执行 IO 是安全的。
空间使用情况
下表提供了使用 Pixel 的操作系统和 OTA 大小的不同 OTA 机制的空间使用情况比较。
大小影响 | 非 A/B | A/B | Virtual A/B | Virtual A/B(压缩) |
---|---|---|---|---|
原始出厂映像 | 4.5GB super (3.8G 映像 + 700M 保留空间)1 | 9GB super(3.8G + 700M 保留空间,用于两个槽位) | 4.5GB super (3.8G 映像 + 700M 保留空间) | 4.5GB super (3.8G 映像 + 700M 保留空间) |
其他静态分区 | /cache | 无 | 无 | 无 |
OTA 期间的额外存储空间(应用 OTA 后返回的空间) | /data 上 1.4GB | 0 | /data 上 3.8GB2 | /data 上 2.1GB2 |
应用 OTA 所需的总存储空间 | 5.9GB3 (super 和 data) | 9GB (super) | 8.3GB3 (super 和 data) | 6.6GB3 (super 和 data) |
1表示基于 Pixel 映射的假设布局。
2假设新的系统映像与原始映像大小相同。
3空间需求是临时的,直到重新启动。
Android 11 Virtual A/B
Android 11 的 Virtual A/B 使用内核 COW 格式写入动态分区。这最终被弃用,因为内核 COW 格式不支持压缩。
Android 12 Virtual A/B
在 Android 12 中,以 Android 特定 COW 格式的形式支持压缩。此版本的 Virtual A/B 需要将 Android 特定 COW 格式转换为内核 COW 格式。最终,这在 Android 13 中被取代,Android 13 取消了对内核 COW 格式和 dm-snapshot
的依赖。
要实现 Virtual A/B 或使用压缩快照功能,请参阅 实现 Virtual A/B