Zygote
Bootintermediate

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.

14 minAPI 1–37 · 17interactivequizverified 2026-10-27

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 /init from the ramdisk as PID 1. init mounts the partitions, loads the SELinux policy, then re-executes itself to parse init.rc.
  • preloads the framework once and forks every app process; system_server is 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#

Loading diagram…
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:

  1. Initialises memory. The kernel expects RAM to be configured; the bootloader sets up the DRAM controller.
  2. Brings up the trusted execution environment and binds the device's root of trust. This is where hardware-backed keys become available.
  3. Verifies the boot partitions. It checks boot, dtbo, init_boot and recovery, including the boot image header version, against the metadata in vbmeta.2
  4. 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, /proc and /sys.
  • It mounts the partitions that contain system code — /system, /vendor, and friends — which on a dynamic-partition device means reading the super metadata and creating logical partitions with device-mapper first.
  • It re-executes /system/bin/init with selinux_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-fatal

The 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_secondary

That 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.

Kernel versions14 releases

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_boot partition, 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#

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_completed has a sibling. dev.bootcomplete is set earlier, by init, before the framework is ready — a distinction that has confused more than one bug report.

Try it#

Watch the boot as properties

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

1 / 12

Which stage runs first after the power button is pressed?

0 answered · 0 correct

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.

  1. docsAndroid platform architecture (AOSP)source.android.comaccessed 2026-10-27↩
  2. docsBootloader overviewsource.android.comaccessed 2026-10-27↩
  3. docsVerified boot flowsource.android.comaccessed 2026-10-27↩
  4. sourcesystem/core/init/README.mdandroid.googlesource.comaccessed 2026-10-27↩
  5. sourcesystem/core/rootdir/init.rcandroid.googlesource.comaccessed 2026-10-27↩
  6. docsZygotesource.android.comaccessed 2026-10-27↩
  7. docsVerified bootsource.android.comaccessed 2026-10-27↩