Partitions and why there are so many
A modern Android device has around twenty partitions, and the list keeps changing. Each one exists because some part of the system needed to be updated, verified or mounted independently of the rest.
Ask someone to list Android's partitions and you will get a different answer depending on which year they learned Android. The core set — boot, system, vendor, userdata — is stable. Everything around it has been added, split, merged, or replaced by a logical partition inside a container.
The reason is always the same: a partition is an update boundary. If two pieces of software can be updated independently, they need separate regions. If they must be signed by different authorities, they need separate regions. If one must be verified and another must be writable, they need separate regions. Read the partition table as a diagram of who owns what and what can change without touching the rest.
TL;DR#
- A partition is a named region of storage with a role. Android's set divides into boot-critical, system, vendor and data.
- Boot-critical:
boot(the GKI kernel),init_boot(the generic ramdisk since Android 13),vbmeta(signature metadata),dtbo(device-tree overlays). - System:
system,system_ext,product. Vendor:vendor,odm,vendor_dlkm. On devices with (Android 10+), these live as logical partitions insidesuper. userdatais the only large writable partition. It holds apps and their data, and it is encrypted;metadataholds the key that encrypts its filesystem metadata.- Modern devices use A/B slots — the same partition name with
_aand_bsuffixes — so an update can target the inactive copy.
Mental model#
Think of a phone's storage as a warehouse divided into rooms.
Some rooms are sealed at the factory and only opened by a signature check: they hold the boot code and the signature manifest. Some rooms are the product itself: the system, the vendor add-ons, the product customisations, each owned by a different team. One room is the customer's: everything the user installs and creates, and it is locked with a key that only the user's credential can turn.
Then two things complicate the building.
First, the walls move. Instead of fixed rooms, the building has one large hall (super) with movable partitions, and a floor plan written on a sign at the entrance. First-stage init reads the sign and builds the walls before anyone moves in.
Second, every room has a twin. The building keeps two identical layouts, A and B, so one can be renovated while the other is in use.
Where the analogy breaks#
Warehouse rooms have doors. Partitions have no intrinsic access control — a process with the right SELinux domain and a device node can read any block. The isolation comes from the filesystem mounts and the policy, not from the partition boundary itself.
The other break is that partitions are not all the same kind of thing. super is a container, not a filesystem. vbmeta is metadata about other partitions. userdata is a filesystem that is encrypted. Treating them as a uniform list of "rooms" hides the fact that they are three different mechanisms wearing the same name.
The big picture#
Text description of this diagram
A treemap of Android's partitions grouped by role. Boot-critical contains boot, init_boot, vbmeta and dtbo. System contains system, system_ext, product and the super container. Vendor contains vendor, odm, vendor_dlkm and system_dlkm. Data and metadata contains userdata, metadata and cache. The treemap areas show the number of partitions in each group, not their byte sizes; real sizes vary enormously between devices and between OEMs.
The treemap deliberately does not show sizes, because sizes are the least portable fact in this area. A system partition on one device is a different size from the same partition on another, and on a dynamic-partition device it may not have a fixed size at all. What is portable is the role of each partition, and the grouping above is the role map.
Deep dive#
The core set#
boot contains the kernel. On a GKI device that is the Generic Kernel Image, built from the Android Common Kernel and signed by Google on devices that ship the Google-signed boot image. On Android 12 and earlier it also held the generic ramdisk.
init_boot was added in Android 13 to hold the generic ramdisk on its own. The motivation is the same as GKI's: the ramdisk changes more often than the kernel, and putting it in boot meant a ramdisk change required a new kernel image and a new signature. Splitting it lets each be updated on its own cadence.
vbmeta holds the Verification Boot metadata: the signed structure containing hash and hashtree descriptors for the other partitions. On a device with chained partitions there may be more than one — vbmeta_system, vbmeta_vendor — each delegating authority for a subset.1
dtbo holds device-tree overlays. A device tree describes hardware to the kernel: which address a controller lives at, which interrupt it uses, which GPIO powers a sensor. Overlays let one kernel image describe several board variants, which is another enabler for GKI.
userdata is the writable partition. It holds /data: installed APKs, app sandboxes, the media store, the settings databases, and the per-user encrypted storage. It is the only partition whose contents the user creates rather than the OEM.
metadata exists because of encryption. With metadata encryption, the userdata filesystem's metadata — directory structure, filenames, file sizes — is itself encrypted with a key stored on a separate partition. That way, an attacker who images the flash cannot even see what files exist.2
cache was where OTA packages were staged before installation. A/B updates removed the need, and the AOSP partition documentation calls it optional on devices that use seamless updates.
The Treble split, expressed as partitions#
Treble's contribution to the partition table was to give the vendor its own space:
| Partition | Owner | Contents |
|---|---|---|
system | AOSP / Google | Framework, runtime, system apps, primary init.rc |
system_ext | AOSP plus OEM additions | System resources and proprietary system modules |
product | OEM | Product-specific modules not bundled elsewhere |
vendor | SoC vendor / OEM | Vendor binaries, HAL implementations, firmware |
odm | ODM | Customisations on top of the SoC vendor's BSP |
The naming is confusing until you see the rule: system and system_ext are the platform's; vendor and odm are the device's; product is the product's. The vendor interface — VINTF — is the contract between the first group and the second, and the partition boundary is what makes the contract enforceable: the framework cannot accidentally link against a vendor library if it is on a different partition.3
super and dynamic partitions#
Before Android 10, every partition had a fixed size decided at manufacture. If the system image grew between Android versions, the OEM had to reserve slack in every partition, and get it wrong in one direction or the other: too much waste, or an update that does not fit.
Dynamic partitions replace fixed sizes with a container. A single super partition holds the logical partitions — system, vendor, product, system_ext and others — and its metadata records where each one lives. Free space is shared: if product needs to grow and system can shrink, the update can rebalance them.4
The kernel cannot read super; it has no idea what a logical partition is. So first-stage init reads the metadata, creates device-mapper devices for each logical partition, and then mounts them normally. That is why the mount code had to change when dynamic partitions arrived, and why a device with dynamic partitions must boot from a ramdisk rather than directly mounting a system-as-root image.
Sizing super is a genuinely hard problem, and AOSP gives explicit formulas rather than a rule of thumb. The inputs are the factory size, the expected growth, and — for Virtual A/B — how much of /data the update is allowed to use. The uncompressed formula, when relying on /data, is:
FinalDessertSize = FactorySize + (FactorySize * ExpectedGrowth)
Super = Max(FinalDessertUpdate, FinalDessertSize * 2 - AllowedUserdataUse)with a worked example: a 4 GB factory size, 50% growth and 1 GB of allowed userdata use gives an 11 GB super. The page warns explicitly that sizing for the final image alone is insufficient, because the image can grow at any time.5
Module partitions: *_dlkm#
GKI added two partitions that exist purely to hold kernel modules:
system_dlkm holds the GKI kernel's own modules. /system/lib/modules is a symlink to /system_dlkm/lib/modules. It became mandatory for devices with Android 13, so the kernel's modules could be updated with the kernel without touching the system image.
vendor_dlkm holds vendor modules, and odm may hold ODM modules. The paths are /vendor/lib/modules and /odm/lib/modules.
The split matters because of verification. Modules are loaded by init from read-only, verified partitions, so they do not need individual signatures — the partition's dm-verity covers them. That is a considerable simplification compared to signing every .ko file.
A/B slots, seen as partitions#
An A/B device has two copies of every boot-critical and system partition, distinguished by a suffix:
boot_a boot_b
system_a system_b
vendor_a vendor_b
vbmeta_a vbmeta_bThe running slot is reported by ro.boot.slot_suffix as _a or _b. The bootloader reads slot metadata to decide which to boot; update_engine writes to the other one.
The metadata itself lives in a small partition — traditionally misc — that both the bootloader and Android can read. It records the active slot, the bootable flag and the successful flag for each slot. This is the handshake that makes rollback possible: a slot that has been booted but not marked successful is on probation.
With Virtual A/B, the duplication applies only to the partitions the bootloader reads.67 The system partitions are not duplicated; the update is written as a snapshot in /data and merged after a successful boot. That is why a Virtual A/B device's partition list looks shorter than a legacy A/B device's, even though it behaves the same way.
What lives inside an APEX#
One more container deserves a mention, because it changes the answer to "where does this code live". An APEX is a signed file containing a small ext4 image, mounted at boot by apexd at /apex/<name>@<version>. Inside it can be libraries, binaries, HALs, or the runtime itself.8
An APEX is not a partition, but it behaves like one: it is verified, mounted read-only, and updated independently. From the partition table's point of view it is just a file in /system/apex or /data/apex. From the update story's point of view it is another independently updatable unit — which is the whole point of Mainline.
How it evolved#
| API | Version | What changed |
|---|---|---|
| 19 | 4.4 KitKat |
|
| 23 | 6.0 Marshmallow |
|
| 24 | 7.0 Nougat |
|
| 26 | 8.0 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
The partition table's history is a history of splitting:
- Android 8.0 split
vendorfromsystemand addedproduct; AVB addedvbmeta. - Android 10 introduced dynamic partitions and
super. - Android 11 introduced Virtual A/B, which removed the need for a duplicated system partition.
- Android 12 made GKI mandatory on 5.10+ devices, and with it
system_dlkm. - Android 13 moved the ramdisk to
init_bootand madesystem_dlkmmandatory. - Android 13 onward made EROFS the default read-only filesystem for new GMS devices, which changed how the system image is built rather than how it is partitioned.9
Each split happened for the same reason: something needed to be updated on a different schedule, or signed by a different authority, or verified separately.
Under the hood#
- first_stage_mount.cppturning fstab entries into mounts, including dm-verity and logical partitions
- dm.cppthe device-mapper calls that create logical partitions and verity devices
- apexd.cpphow an APEX becomes a mounted directory
- fs_avb.cppfinding and verifying the vbmeta structures
- README.mdwhere first-stage init fits in the boot
Curiosity corner#
superis not a filesystem. It is a container with metadata; the partitions inside it are created with device-mapper at boot, so they look like block devices to everything above.cacheis disappearing. On A/B devices it is optional, and many modern devices do not have one at all.- The
_a/_bsuffix is why flashing instructions sayboot_a. Flashingbootwithout a suffix on a slotted device either fails or writes to the wrong slot, which is a reliable way to make a device unbootable. - A GSI is a system image that boots on any Treble device. It is the practical demonstration that the vendor interface is a real boundary rather than a naming convention.
Try it#
adb shell ls -l /dev/block/by-name/
adb shell cat /proc/partitions
adb shell getprop ro.boot.slot_suffix
adb shell getprop ro.boot.dynamic_partitions
adb shell df -h /data /system
Example output
lrwxrwxrwx 1 root root 16 boot_a -> /dev/block/sde11
lrwxrwxrwx 1 root root 16 boot_b -> /dev/block/sde12
lrwxrwxrwx 1 root root 16 init_boot_a -> /dev/block/sde9
lrwxrwxrwx 1 root root 16 super -> /dev/block/sde20
lrwxrwxrwx 1 root root 16 userdata -> /dev/block/sda1
lrwxrwxrwx 1 root root 16 vbmeta_a -> /dev/block/sde7
...
super 4096 0 0/dev/block/by-name/ is the friendly view: symlinks named after partitions, pointing at the real block device. The names come from the device tree or the fstab, not from the kernel, which is why they are stable across devices while the underlying device numbers are not.
Check yourself#
Check yourself
Which stage runs first after the power button is pressed?
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsAndroid Verified Boot (AVB)source.android.comaccessed 2026-10-27↩
- docsFile-Based Encryption (FBE)source.android.comaccessed 2026-10-27↩
- docsModular system componentssource.android.comaccessed 2026-10-27↩
- docsDynamic partitionssource.android.comaccessed 2026-10-27↩
- docsSize the super partitionsource.android.comaccessed 2026-10-27↩
- docsA/B (seamless) system updatessource.android.comaccessed 2026-10-27↩
- docsVirtual A/Bsource.android.comaccessed 2026-10-27↩
- docsAPEX file formatsource.android.comaccessed 2026-10-27↩
- docsEROFS filesystemsource.android.comaccessed 2026-10-27↩