虚拟 A/B 概览

虚拟 A/B ��� Android 的主要更新机制。虚拟 A/B 是在旧版 A/B 更新(请参阅 A/B 系统更新)和非 A/B 的基础上构建的;后者已在 Android 15 中废弃,以减少更新的空间开销。

虚拟 A/B 实际上并没有额外的动态分区槽位,请参阅动态分区。相反,增量会写入快照,然后在确认成功启动后合并到基本分区。虚拟 A/B 使用 Android 专用的快照格式。请参阅压缩快照的 COW 格式,该格式支持压缩快照并最大限度地减少磁盘可用空间用量。在完整 OTA 中,快照大小会通过压缩减小约 45%;在增量 OTA 中,快照大小可减小约 55%。

Android 12 提供了虚拟 A/B 压缩选项以压缩快照分区。虚拟 A/B 提供以下好处

  • 虚拟 A/B 更新与 A/B 更新一样,都是无缝更新(更新完全在后台进行,不会影响设备运行)。虚拟 A/B 更新可最大限度地缩短设备离线和不可用的时间。
  • 虚拟 A/B 更新可以回滚。如果新操作系统无法启动,设备将自动回滚到先前版本。
  • 通过仅复制引导加载程序使用的分区,虚拟 A/B 更新使用极少的额外空间。对于其他可更新分区,将会拍摄快照

背景信息和术语

本部分介绍了相关术语和支持虚拟 A/B 的技术。 在 OTA 安装期间,新的操作系统数据会写入物理分区的新槽位,或写入 Android 专用的 COW 设备。设备重新启动后,动态分区数据会通过使用 dm-user 和 snapuserd 守护程序重新合并到其基础设备中。此过程完全在用户空间中进行。

设备映射器

设备映射器是 Android 中常用的一个 Linux 虚拟块层。使用动态分区时,类似 /system 的分区是分层设备的堆栈:

  • 堆栈的底部是物理 super 分区(例如 /dev/block/by-name/super)。
  • 中间是 dm-linear 设备,指定了 super 分区中的哪些块构成给定的动态分区。这在 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 装载点下的堆栈是什么样子。

system 下的分区堆栈

图 1. /system 装载点下的堆栈

压缩快照

在 Android 12 及更高版本中,由于 /data 分区上的空间要求可能较高,因此您可以在 build 中启用压缩快照,以满足 /data 分区更高的空间要求。

虚拟 A/B 压缩快照基于 Android 12 及更高版本中提供的以下组件构建而成:

  • dm-user:一个类似于 FUSE 的内核模块,可以让用户空间实现块存储设备。
  • snapuserd:用于实现新快照格式的用户空间守护程序。

这些组件可实现上述压缩功能。下文将给出实现压缩快照功能所需的其他必要更改:压缩快照的 COW 格式dm-usersnapuserd

压缩快照的 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 更新还可以包含复制操作。

磁盘上的完整快照布局如下所示:

cow 格式

图 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-usersnapuserd 用户空间组件实现了虚拟 A/B 压缩。 snapuserd 是一个用户空间守护程序,负责写入和读取 Android COW 设备。对快照的所有 I/O 都必须通过此服务进行。在 OTA 安装期间,新的操作系统数据由 snapuserd 写入快照(数据会经过压缩)。此处还会处理元数据的解析和新块数据的解压缩。

XOR 压缩

对于发布时搭载 Android 13 及更高版本的设备,XOR 压缩功能(默认处于启用状态)会启用用户空间快照,以在旧块和新块之间存储 XOR 压缩字节。如果虚拟 A/B 更新只更改了某个块中的一些字节,XOR 压缩存储方案所用的空间会少于默认存储方案,因为快照不会存储所有 4K 字节。这样可以缩减快照大小,因为 XOR 数据包含许多零,并且比原始块数据更易于压缩。在 Pixel 设备上,XOR 压缩可以将快照大小缩减 25% 到 40%。

对于升级到 Android 13 及更高版本的设备,必须启用 XOR 压缩。如需了解详情,请参阅 XOR 压缩

快照合并

对于发布时搭载 Android 13 及更高版本的设备,虚拟 A/B 压缩中的快照和快照合并过程由 snapuserd 用户空间组件执行。对于升级到 Android 13 及更高版本的设备,必须启用此功能。如需了解详情,请参阅用户空间合并

下面介绍了虚拟 A/B 压缩过程:

  1. 框架会在 dm-verity 设备之外装载 /system 分区,该设备堆叠在 dm-user 设备上。也就是说,根文件系统中的每个 I/O 都会路由到 dm-user
  2. dm-user 将 I/O 路由到用户空间 snapuserd 守护程序,该守护程序会处理 I/O 请求。
  3. 合并操作完成后,框架将 dm-verity 堆叠在 dm-linear (system_base) 上,并移除 dm-user

虚拟 A/B 压缩过程

图 3. 虚拟 A/B 压缩过程

快照合并过程可能会中断。如果设备在合并过程中重新启动,合并过程将在重新启动后继续进行。

Init 转换

使用压缩快照启动时,第一阶段 init 必须启动 snapuserd 才能装载分区。这会产生一个问题:当加载并强制执行 sepolicy 时,snapuserd 会被放入错误的上下文中,其读取请求失败,并会导致 SELinux 拒绝事件。

为解决此问题,snapuserdinit 同步转换,如下所示:

  1. 第一阶段 init 从 ramdisk 启动 snapuserd,并将一个打开文件描述符以环境变量的形式保存到其中。
  2. 第一阶段 init 将根文件系统切换到系统分区,然后执行 init 的系统�����。
  3. init 的系统副本会将合并后的 sepolicy 读入字符串中。
  4. Init 会在所有由 ext4 支持的网页上调用 mlock()。然后,它会停用快照设备的所有设备映射器表,并停止 snapuserd。在此之后,禁止从分区读取数据,因为这样做会导致死锁。
  5. snapuserd 的 ramdisk 副本使用打开描述符,init 会使用正确的 SElinux 上下文重新启动守护程序。快照设备的设备映射器表被重新激活。
  6. Init 调用 munlockall(),再次安全地执行 IO 是安全的。

存储空间使用情况

下表比较了使用 Pixel 的操作系统和 OTA 大小的不同 OTA 机制的存储空间使用情况。

对应用大小的影响 非 A/B A/B 虚拟 A/B 虚拟 A/B(压缩)
原始出厂映像 4.5GB 超级数据(3.8G 图片 + 700M 预留)1 9GB 超级数据(3.8G + 700M 预留,两个插槽) 4.5GB 超级数据(3.8G 图片 + 700M 预留) 4.5GB 超级数据(3.8G 图片 + 700M 预留)
其他静态分区 /cache
OTA 期间的额外存储空间(应用 OTA 后返回的空间) /data 上的存储空间为 1.4GB 0 /data 上的存储空间为 3.8GB2 /data 上的存储空间为 2.1GB2
应用 OTA 所需的总存储空间 5.9GB3(超级数据和数据) 9GB(超级数据) 8.3GB3(超级数据和数据) 6.6GB3(超级数据和数据)

1表示基于像素映射的假设布局。

2假设新系统映像与原始系统映像的大小相同。

3在重新启动之前,空间要求是暂时的。

Android 11 虚拟 A/B

使用虚拟 A/B 的 Android 11 利用内核 COW 格式写入动态分区。此方法最终被废弃,因为内核 COW 格式不支持压缩。

Android 12 虚拟 A/B

在 Android 12 中,支持以 Android 专用的 COW 格式进行压缩。虚拟 A/B 的此版本需要将 Android 专用的 COW 格式转换为内核 COW 格式。最终,在 Android 13 中,此方法被替换,移除了对内核 COW 格式和 dm-snapshot 的依赖。

如需实现虚拟 A/B 或使用压缩快照功能,请参阅实现虚拟 A/B