The boot sequence
From a press of the power button to the first frame of the home screen: eight stages, four signed handoffs, and a device that has to prove what it is running before it is allowed to run it.
A phone boots in roughly the time it takes to read a paragraph, and in that time it has to do something no server ever does: prove to itself, using a chain of signatures rooted in hardware, that every byte of code it is about to run is the code the owner authorised. Then it has to start a hundred services in an order that respects their dependencies, mount encrypted storage, and draw a home screen.
This page is the map. The three pages after it zoom into the parts that deserve their own treatment: init and init.rc, verified boot and A/B updates, and partitions.
TL;DR#
- Boot is a relay of handoffs: boot ROM → bootloader → kernel → init (first stage) → init (second stage) → zygote →
system_server→ SystemUI and the launcher. - The bootloader is vendor code. It initialises memory, brings up the trusted execution environment, verifies the boot partitions, picks the A/B slot and loads the kernel.
- The kernel runs
/initfrom the ramdisk as PID 1. init mounts the partitions, loads the SELinux policy, then re-executes itself to parseinit.rc. - preloads the framework once and forks every app process;
system_serveris forked from it and starts the system services in phases. - Verification is continuous, not a single check: the bootloader verifies boot partitions, and verifies system blocks lazily as they are read.
Mental model#
Think of a relay race where each runner must check the baton.
The first runner (the boot ROM) cannot be substituted; it is etched into the processor. It picks up the baton from a fixed location and hands it to the second runner (the bootloader) only after checking a signature.
The second runner has the hardest job. It sets up the track, checks the baton's signature against a key it trusts, and only then hands over to the third (the kernel). It also decides which lane to run in — that is the A/B slot.
The third runner builds the environment the rest will run in: memory management, drivers, the first user-space process. From here the baton stops being a physical object and becomes a process tree.
The fourth runner (init) is a stage manager. It mounts what needs mounting, sets the rules (SELinux), and then reads a script that says who starts when.
The fifth (zygote) is the template: one warm process from which every app is copied.
The sixth (system_server) is the control room: the services everything else calls.
The seventh and eighth (SystemUI and the launcher) are the visible result.
Where the analogy breaks#
A relay is sequential, and boot is not entirely. init starts services by class and by trigger, so many things happen in parallel once the early stages are done. zygote and the HAL daemons can be starting at the same moment; the ordering that matters is expressed as dependencies ("this must wait for post-fs-data"), not as a strict queue.
The verification is also not a single baton check. It is a chain that continues after the kernel is running: dm-verity verifies blocks on demand, long after the bootloader has finished.
The big picture#
Text description of this diagram
A left-to-right pipeline of eight boot stages. The SoC boot ROM loads the bootloader from a fixed location. The bootloader initialises memory, verifies the boot partitions, picks the A/B slot and loads the kernel and ramdisk. The kernel initialises memory management, the scheduler and drivers, then executes /init as PID 1. First-stage init mounts /dev, /proc and the partitions holding system code, then re-executes itself to load SELinux policy. Second-stage init parses init.rc and starts services. Zygote preloads framework classes and waits to fork app processes. system_server starts the system services and registers them with servicemanager. SystemUI and the launcher draw the first frame. Each stage produces exactly what the next stage needs.
The diagram compresses a lot. Two things it cannot show are worth stating plainly.
First, the boundaries between stages are trust boundaries. Between stage 2 and 3, the bootloader has verified the kernel's signature. Between stage 3 and 4, the kernel has mounted a verified root filesystem. Between stage 4 and 5, SELinux is loaded, so everything after runs confined.
Second, the stages do not all finish before the next begins. The kernel starts init as soon as it can run user space; init starts zygote before the boot is fully complete; the launcher draws while background services are still initialising.
The layering in that diagram is the same one the platform documentation draws: kernel, HAL, native daemons and libraries, the Android runtime, the framework, and apps. Boot is the process of assembling those layers in order, from the bottom up.1
Deep dive#
Stage 1 and 2: the boot ROM and the bootloader#
The boot ROM is mask ROM inside the SoC. It is not updatable, which is precisely why it is trusted: an attacker cannot change it without physical access to the silicon. It knows how to load the next stage from a fixed location — usually eMMC or UFS, sometimes over USB in a recovery mode — and it verifies it if the platform supports it.
The bootloader is vendor-proprietary code. It does four things before the kernel exists:
- Initialises memory. The kernel expects RAM to be configured; the bootloader sets up the DRAM controller.
- Brings up the trusted execution environment and binds the device's root of trust. This is where hardware-backed keys become available.
- Verifies the boot partitions. It checks
boot,dtbo,init_bootandrecovery, including the boot image header version, against the metadata invbmeta.2 - Selects the slot. On an A/B device it decides which slot to boot, then loads the kernel as a self-decompressing image along with the ramdisk and bootconfig.
The bootloader reports what it decided through kernel command-line parameters or bootconfig. androidboot.verifiedbootstate is green (locked, normal root of trust), yellow (locked, custom root of trust) or orange (unlocked). androidboot.veritymode is eio or restart, and controls what dm-verity does when it finds corruption.3
Stage 3: the kernel#
The kernel decompresses itself, initialises the memory manager and scheduler, probes the hardware it can discover from the device tree, mounts the initramfs, and executes /init.
Two kernel details matter for boot time. First, the kernel is now GKI: on a device shipping Android 12 or later with a 5.10+ kernel, the kernel image is the generic one and board support arrives as loadable modules. Second, the kernel's job is not to know about Android. It starts one process and gets out of the way.
The kernel's command line and bootconfig carry the bootloader's decisions into user space. That is how init learns which slot is active and what the verification state was.
Stage 4: first-stage init#
/init in the ramdisk is a minimal program. Its job is to make the rest of the boot possible:
- It mounts
/dev,/procand/sys. - It mounts the partitions that contain system code —
/system,/vendor, and friends — which on a dynamic-partition device means reading thesupermetadata and creating logical partitions with device-mapper first. - It re-executes
/system/bin/initwithselinux_setup, which loads the SELinux policy. - It re-executes again with
second_stage.
It deliberately does not mount /data. That comes later, after vold and the keymaster are available, because /data is encrypted and decrypting it requires services that do not exist yet.4
Stage 5: second-stage init and init.rc#
Second-stage init is where the boot becomes visible. It parses /system/etc/init/hw/init.rc and imports every *.rc from /system/etc/init, /system_ext/etc/init, /vendor/etc/init, /odm/etc/init, /product/etc/init, and from APEXes under /apex/*/etc/*rc.5
The language has five statement types — actions, commands, services, options and imports — plus triggers that attach conditions to actions.
# illustrative: the shape of a service definition
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
onrestart write /sys/power/state on
critical window=${zygote.critical_window.minute:-off} target=zygote-fatalThe real file is worth reading, and is cited below. The important idea is that ordering is declarative. The rc files do not say "start A, then B, then C"; they say "this service belongs to class main" and "this action runs on the boot trigger". init works out the order.
The built-in boot triggers form a spine:
early-init → init → late-init → early-fs → fs → post-fs → late-fs → post-fs-data → load-bpf-programs → bpf-progs-loaded → zygote-start → early-boot → boot
late-init is a dispatcher: it triggers the whole chain in order. /data is mounted on post-fs-data, and zygote starts on zygote-start.
Stage 6: zygote#
Zygote is a process that exists to be copied. It loads the framework's classes and resources once, then listens on a socket. When the framework asks for a new app process, zygote forks, and the child inherits the preloaded heap through copy-on-write pages.
init starts it on the zygote-start trigger, which also runs update_verifier — the program that marks a newly updated slot as successful. The zygote service definition includes --start-system-server, which tells it to fork system_server once it is ready.6
on zygote-start
wait_for_prop odsign.verification.done 1
# A/B update verifier that marks a successful boot.
exec_start update_verifier
start statsd
start zygote
start zygote_secondaryThat trigger is a good example of how ordering is enforced. wait_for_prop blocks until an APEX signature check has finished, because starting zygote before the runtime APEX is verified would defeat the point of verifying it.
Stage 7: system_server#
Forked from zygote, system_server is an ordinary process that happens to run about a hundred services. It starts them in phases: bootstrap services first (ActivityManager, PackageManager), then core, then the rest, with dependencies between them. Each service that other processes need registers a Binder name with as it starts, which is what makes getSystemService work.
system_server crashing is not survivable in the way a system app crashing is. init has a critical directive on some services that causes a reboot if they crash repeatedly, and zygote's definition includes a critical window for exactly this reason.
Stage 8: the first frame#
SystemUI starts and draws the status bar and navigation bar. The launcher — an ordinary app with a CATEGORY_HOME activity — starts and draws the home screen. When its first frame reaches the display, the boot is over.
The property sys.boot_completed is set to 1 at this point, and much of the system listens for it. It is the closest thing Android has to a "boot finished" signal, and it is approximately, not exactly, when the user sees the home screen.
How it evolved#
Boot has changed less than you might expect at the top, and more than you might expect at the bottom.
| API | Version | Kernel |
|---|---|---|
| 24 | 7.0 Nougat |
|
| 25 | 7.1 Nougat |
|
| 26 | 8.0 Oreo |
|
| 27 | 8.1 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 32 | 12L |
|
| 33 | 13 Tiramisu |
|
| 34 | 14 Upside Down Cake |
|
| 35 | 15 Vanilla Ice Cream |
|
| 36 | 16 Baklava |
|
| 37 | 17 Cinnamon Bun |
|
The structural changes are all about what the bootloader must verify and where the kernel comes from:
- Android 7.0 introduced A/B updates and the idea that a failed boot could roll back.
- Android 8.0 introduced Treble and AVB 2.0, which made verified boot a chain of partitions rather than one signed image.
- Android 10 introduced dynamic partitions, so the partition layout is created at boot rather than at manufacture.
- Android 12 made GKI mandatory for devices on 5.10 or higher, which decoupled the kernel from the vendor image.
- Android 13 moved the generic ramdisk to its own
init_bootpartition, so the ramdisk can be updated without a kernel.
Each of those changed what the bootloader does and what init must find when it mounts.
Verified boot is the thread that connects them: Android 4.4 added dm-verity and the first verified boot, and AVB 2.0 with Android 8.0 standardised the signed metadata the bootloader reads.7
Under the hood#
- README.mdthe three phases of init and the full language reference
- init.rcthe primary boot script, including the late-init trigger chain
- init.zygote64.rcthe zygote service definition
- first_stage_init.cppmounting the system partitions from the ramdisk
- first_stage_mount.cpphow the fstab entries become mounts
Curiosity corner#
- init is PID 1, and that is not an accident. The kernel executes exactly one user-space program at boot; everything else descends from it. That is why init is also the process that reaps orphaned children and handles reboot requests.
- The boot animation is not part of the boot. It is a service like any other, reading a zip of PNGs or a compiled animation, and it stops when a property changes.
sys.boot_completedhas a sibling.dev.bootcompleteis set earlier, by init, before the framework is ready — a distinction that has confused more than one bug report.
Try it#
adb shell getprop ro.boot.verifiedbootstate
adb shell getprop ro.boot.slot_suffix
adb shell getprop sys.boot_completed
adb shell getprop | grep boottime
adb shell ps -A | grep -E "init|zygote|system_server"
Example output
[ro.boot.verifiedbootstate]: [green]
[ro.boot.veritymode]: [enforcing]
[ro.boot.slot_suffix]: [_a]
[init.svc.zygote]: [running]
[init.svc.surfaceflinger]: [running]
[sys.boot_completed]: [1]
[ro.boottime.zygote]: [4821902113]
[ro.boottime.system_server]: [5213447112]The ro.boottime.* properties are set by init and record, in nanoseconds since boot, when each service started. Sorting them gives you a boot timeline without a trace. ro.boottime.zygote is a useful proxy for "how long until the framework could start".
Build it#
The N01 lab builds AOSP and boots it on an emulator, which is the only reliable way to watch the whole sequence with your own changes in it.
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 platform architecture (AOSP)source.android.comaccessed 2026-10-27↩
- docsBootloader overviewsource.android.comaccessed 2026-10-27↩
- docsVerified boot flowsource.android.comaccessed 2026-10-27↩
- sourcesystem/core/init/README.mdandroid.googlesource.comaccessed 2026-10-27↩
- sourcesystem/core/rootdir/init.rcandroid.googlesource.comaccessed 2026-10-27↩
- docsZygotesource.android.comaccessed 2026-10-27↩
- docsVerified bootsource.android.comaccessed 2026-10-27↩