Virtual A/B overview

Virtual A/B is Android's main update mechanism. Virtual A/B builds on top of legacy A/B updates (see A/B System Updates) and non-A/B which is deprecated in 15 to reduce the space overhead of updates.

Virtual A/B doesn't actually have an extra slot for dynamic partitions, see dynamic partitions. Instead the delta is written to a snapshot, and then merged into the base partition after confirming a successful boot. Virtual A/B uses an Android specific snapshot format. See COW format for compressed snapshots which allows for snapshots to be compressed and minimizes disk space usage. On a full OTA the snapshot size is reduced by around 45% with compression, and incremental OTA snapshot size is reduced by around 55%.

Android 12 offers the option of Virtual A/B compression to compress snapshotted partitions. Virtual A/B offers the following

  • Virtual A/B updates are seamless (the update happens entirely in the background while the device is operational) like A/B updates. Virtual A/B updates minimize the time that a device is offline and unusable.
  • Virtual A/B updates can be rolled back. If the new OS fails to boot, devices automatically roll back to the previous version.
  • Virtual A/B updates use a minimum of extra space by duplicating only the partitions that are used by the bootloader. Other updateable partitions are snapshotted.

Background and terminology

This section defines the terminology and describes the technology that supports virtual A/B. During OTA installation, new operating system data is either written to its new slot for physical partitions, or an android specific COW device. After the device is rebooted, dynamic partition data is merged back into its base device through the usage of dm-user and snapuserd daemon. This process happens entirely in userspace.

Device-mapper

Device-mapper is a Linux virtual block layer used often in Android. With dynamic partitions, partitions like /system are a stack of layered devices:

  • At the bottom of the stack is the physical super partition (for example, /dev/block/by-name/super).
  • In the middle is a dm-linear device, specifying which blocks in the super partition form the given dynamic partition. This appears as /dev/block/mapper/system_[a|b] on an A/B device, or /dev/block/mapper/system on a non-A/B device.
  • At the top resides a dm-verity device, created for verified partitions. This device verifies that blocks on the dm-linear device are signed correctly. It appears as /dev/block/mapper/system-verity and is the source of the /system mount point.

Figure 1 shows what the stack under the /system mount point looks like.

Partition stacking underneath
system

Figure 1. Stack under the /system mount point

Compressed snapshots

In Android 12 and higher, because space requirements on the /data partition can be high, you can enable compressed snapshots in your build to address the higher space requirements of the /data partition.

Virtual A/B compressed snapshots are built on top of the following components that are available in Android 12 and higher:

  • dm-user, a kernel module similar to FUSE that allows userspace to implement block devices.
  • snapuserd, a userspace daemon to implement a new snapshot format.

These components enable the compression. The other necessary changes made to implement the compressed snapshots capabilities are given in the next sections: COW format for compressed snapshots, dm-user, and snapuserd.

COW format for compressed snapshots

In Android 12 and higher, compressed snapshots use an Android specific COW format. The COW Format contains metadata about the OTA and has distinct buffers containing COW operations and new operating system data. Compared to the kernel snapshot format which only allowed for replace operations (Replace block X in the base image with the contents of block Y in the snapshot), The Android compressed snapshots COW format is more expressive and supports the following operations:

  • Copy: Block X in the base device should be replaced with block Y in the base device.
  • Replace: Block X in the base device should be replaced with the contents of block Y in the snapshot. Each of these blocks is gz compressed.
  • Zero: Block X in the base device should be replaced with all zeroes.
  • XOR: The COW device stores XOR compressed bytes between block X and block Y. (Available in Android 13 and higher.)

Full OTA updates consist of replace and zero operations only. Incremental OTA updates can additionally have copy operations.

The full snapshot layout on disk looks like this:

cow format

Figure 2. Android COW Format on Disk

dm-user

The dm-user kernel module enables userspace to implement device-mapper block devices. A dm-user table entry creates a miscellaneous device under /dev/dm-user/<control-name>. A userspace process can poll the device to receive read and write requests from the kernel. Each request has an associated buffer for userspace to either populate (for a read) or propagate (for a write).

The dm-user kernel module provides a new user-visible interface to the kernel that isn't part of the upstream kernel.org code base. Until it is, Google reserves the right to modify the dm-user interface in Android.

snapuserd

The snapuserd userspace component to dm-user implements Virtual A/B compression. Snapuserd is a userspace daemon in charge of writing and reading the Android COW devices. All I/O to the snapshot must go through this service. During OTA installation, new operating system data is written to the snapshot by snapuserd (with compression). The parsing of the metadata and unpacking of new block data is also handled here.

XOR compression

For devices launching with Android 13 and higher, the XOR compression feature, which is enabled by default, enables userspace snapshots to store XOR compressed bytes between old blocks and new blocks. When only a few bytes in a block are changed in a Virtual A/B update, the XOR compression storage scheme uses less space than the default storage scheme because snapshots don't store full 4K bytes. This reduction in snapshot size is possible because XOR data contains many zeros and is easier to compress than raw block data. On Pixel devices, XOR compression reduces snapshot size by 25% to 40%.

For devices upgrading to Android 13 and higher, XOR compression must be enabled. For details, see XOR compression.

Snapshot merge

For devices launching with Android 13 and higher, the snapshot and snapshot merge processes in Virtual A/B compression are performed by the snapuserd userspace component. For devices upgrading to Android 13 and higher, this feature must be enabled. For details, see Userspace merge.

The following describes the Virtual A/B compression process:

  1. The framework mounts the /system partition off of a dm-verity device, which is stacked on top of a dm-user device. This means that every I/O from the root file system is routed to dm-user.
  2. dm-user routes the I/O to the userspace snapuserd daemon, which handles the I/O request.
  3. When the merge operation is complete, the framework collapses dm-verity on top of dm-linear (system_base) and removes dm-user.

Virtual A/B compression
process

Figure 3. Virtual A/B compression process

The snapshot merge process can be interrupted. If the device is rebooted during the merge process, the merge process resumes after reboot.

Init transitions

When booting with compressed snapshots, the first-stage init must start snapuserd to mount partitions. This poses a problem: When sepolicy is loaded and enforced, snapuserd gets put in the wrong context, and its read requests fail, with selinux denials.

To address this, snapuserd transitions in lock-step with init, as follows:

  1. First-stage init launches snapuserd from the ramdisk, and saves an open file-descriptor to it in an environment variable.
  2. First-stage init switches the root filesystem to the system partition, then executes the system copy of init.
  3. The system copy of init reads the combined sepolicy into a string.
  4. Init invokes mlock() on all ext4-backed pages. It then deactivates all device-mapper tables for snapshot devices, and stops snapuserd. After this it’s forbidden to read from partitions, since doing so causes deadlock.
  5. Using the open descriptor to the ramdisk copy of snapuserd, init relaunches the daemon with the correct selinux context. Device-mapper tables for snapshot devices are re-activated.
  6. Init invokes munlockall()- it’s safe to perform IO again.

Space usage

The following table provides a comparison of space usage for different OTA mechanisms using Pixel's OS and OTA sizes.

Size Impact non-A/B A/B Virtual A/B Virtual A/B (compressed)
Original Factory Image 4.5GB super (3.8G image + 700M reserved)1 9GB super (3.8G + 700M reserved, for two slots) 4.5GB super (3.8G image + 700M reserved) 4.5GB super (3.8G image + 700M reserved)
Other static Partitions /cache None None None
Additional storage During OTA (space returned after applying OTA) 1.4GB on /data 0 3.8GB2 on /data 2.1GB2 on /data
Total storage required to apply OTA 5.9GB3 (super and data) 9GB (super) 8.3GB3 (super and data) 6.6GB3 (super and data)

1Indicates assumed layout based on Pixel mapping.

2Assumes new system image is the same size as original.

3Space requirement is transient until reboot.

Android 11 Virtual A/B

The android 11 of Virtual A/B wrote to dynamic partition using the Kernel COW format. This was eventually deprecated as the Kernel COW format does not support compression.

Android 12 Virtual A/B

In android 12, compression is supported in the form of an android specific COW format. This version of Virtual A/B required a translation of the android specific COW to the Kernel COW format. Eventually this was replaced in android 13 which removed the reliance on the Kernel COW format and also dm-snapshot.

To implement Virtual A/B, or to use compressed snapshot capabilities, see Implementing Virtual A/B