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 thedm-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.
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:
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:
- The framework mounts the
/system
partition off of adm-verity
device, which is stacked on top of adm-user
device. This means that every I/O from the root file system is routed todm-user
. dm-user
routes the I/O to the userspacesnapuserd
daemon, which handles the I/O request.- When the merge operation is complete, the framework collapses
dm-verity
on top ofdm-linear
(system_base
) and removesdm-user
.
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:
- First-stage
init
launchessnapuserd
from the ramdisk, and saves an open file-descriptor to it in an environment variable. - First-stage
init
switches the root filesystem to the system partition, then executes the system copy ofinit
. - The system copy of
init
reads the combined sepolicy into a string. Init
invokesmlock()
on all ext4-backed pages. It then deactivates all device-mapper tables for snapshot devices, and stopssnapuserd
. After this it’s forbidden to read from partitions, since doing so causes deadlock.- 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. - 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