Android has two update mechanisms: A/B (seamless) updates and non-A/B updates. To reduce code complexity and enhance the update process, in Android 11 the two mechanisms are unified through virtual A/B to bring seamless updates to all devices with a minimized cost of storage. Android 12 offers the option of Virtual A/B compression to compress snapshotted partitions. In both Android 11 and Android 12, the following apply:
- Virtual A/B updates are seamless 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.
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 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
dm-snapshot
Virtual A/B relies on dm-snapshot
, a device-mapper module for snapshotting the
state of a storage device. When using dm-snapshot
, there are four devices in
play:
- The base device is the device that's snapshotted. On this page, the base device is always a dynamic partition, such as system or vendor.
- The copy-on-write (COW) device, for logging changes to the base device. It can be any size, but it must be big enough to accommodate all changes to the base device.
- The snapshot device is created using the
snapshot
target. Writes to the snapshot device are written to the COW device. Reads from the snapshot device read either from the base device or the COW device, depending on whether the data being accessed has been changed by the snapshot. - The origin device is created using the
snapshot-origin
target. Reads to the origin device read directly from the base device. Writes to the origin device write directly to the base device, but the original data is backed up by writing to the COW device.
Figure 2. Device mapping for dm-snapshot
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 a COW format. Similar to the kernel's built-in format used for uncompressed snapshots, the COW format for the compressed snapshots has alternating sections of metadata and data. The original format's metadata allowed only for replace operations: Replace block X in the base image with the contents of block Y in the snapshot. The 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.
dm-user in Android 12
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.
In the uncompressed version of Virtual A/B, (either in Android 11 and lower, or
in Android 12 without the compressed snapshot option),
the COW device is a raw file. When compression is enabled, the COW functions
instead as a dm-user
device, which is connected to an instance of the
snapuserd
daemon.
The kernel doesn’t use the new COW format. So the snapuserd
component
translates requests between the Android COW format and the kernel's built-in
format:
Figure 3. Flow diagram of snapuserd as translator between Android and Kernel COW formats
This translation and decompression never occurs on disk. The snapuserd
component intercepts the COW reads and writes that occur in the kernel, and
implements them using the Android COW format.
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.
Virtual A/B compression processes
This section provides details about the Virtual A/B compression process used in Android 13 and Android 12.
Reading metadata (Android 12)
Metadata is constructed by a snapuserd
daemon. The metadata is primarily a
mapping of two IDs, 8 bytes each, that represent the sectors to be merged.
In dm-snapshot
it’s called disk_exception
.
struct disk_exception {
uint64_t old_chunk;
uint64_t new_chunk;
};
A disk exception is used when an old chunk of data is replaced by a new one.
A snapuserd
daemon reads the internal COW file through the COW library and
constructs the metadata for each of the COW operations present in the COW file.
Metadata reads are initiated from the dm-snapshot
in the kernel when the dm-
snapshot
device is created.
The figure below provides a sequence diagram for the IO path for metadata construction.
Figure 4. Sequence flow for the IO path in metadata construction
Merging (Android 12)
Once the boot process is complete, the update engine marks the slot as boot
successful and initiates the merge by switching the dm-snapshot
target to the
dm-snapshot-merge
target.
dm-snapshot
walks through the metadata and initiates a merge IO for each disk
exception. A high-level overview of the merge IO path is shown below.
Figure 5. Merge IO path overview
If the device is rebooted during the merge process, the merge resumes on the next reboot, and the merge is completed.
Device-mapper layering
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 6. 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.
To implement Virtual A/B, or to use compressed snapshot capabilities, see Implementing Virtual A/B