Map
โ†‘Linux Kernel Startup

Linux boot phases

Wiki conceptlinuxkernelboot โ†ณ show in map Markdown
title
Linux boot phases
type
concept
summary
The six-phase x86_64 Linux boot model โ€” assembly trampoline, early C, setup_arch, start_kernel, rest_init, kernel_init โ€” each phase unlocking the capabilities the next one needs
tags
linux, kernel, boot
created
2026-05-14
updated
2026-05-14

Linux boot on x86_64 is a strictly ordered construction sequence, not a script. Each phase has a different "what can we do" envelope, and the order matters because each step unlocks the capability the next one needs. Espino's framing in linux-kernel-startup groups it into six phases.

The phases

Phase Entry point What becomes possible
1. Assembly startup_64 CPU in Long Mode, page tables fixed, ready to run C
2. Early C x86_64_start_kernel .bss cleared, fault handlers, boot params saved, microcode patched
3. setup_arch setup_arch() CPU features known, memory map parsed, full RAM mapped, early printk
4. start_kernel start_kernel() kmalloc works, scheduler/RCU/timers up, interrupts on, real console, self-patched for this CPU
5. rest_init rest_init() PID 1 (kernel_init) and PID 2 (kthreadd) exist; boot thread becomes idle
6. kernel_init kernel_init_freeable() Other CPUs awake, drivers loaded, root mounted, __init freed, userspace running

Why the order matters

Two ordering rules carry most of the structure:

Interrupts off until everything that takes interrupts exists. Phase 4 runs almost entirely with interrupts disabled. The kernel can't accept an interrupt until the IRQ controller is wired, timers can fire, RCU is alive, the timekeeper has a clock, and the printk machinery can complain on failure. local_irq_enable() happens deep inside start_kernel(), not at its start.

Each allocator hands off to the next. Boot rides on memblock (a tracker, not really an allocator) until mm_core_init() brings the buddy โ†’ SLUB โ†’ vmalloc stack online. memblock_free_all() is the discrete moment ownership transfers; from then on every allocation flows through the page allocator and the slab cache.

The handoff: boot thread becomes idle

The most consequential boundary is between Phase 4 and Phase 5. Throughout Phase 4, a single thread (init_task) is doing all the work. sched_init() quietly relabels it as the boot CPU's idle task โ€” but it keeps running init code. The actual handover happens in rest_init(): schedule_preempt_disabled() yields the CPU, and when control comes back, the original thread enters do_idle() forever. PID 1 (kernel_init) carries the boot work from that point. Same execution gets split into three threads: PID 0 (idle), PID 1 (init โ†’ userspace), PID 2 (kthreadd).

The "lock the doors last" rule

Several hardenings happen in Phase 6 after most code has run because the hardening would prevent the running code:

  • mark_readonly() flips rodata to read-only at the page-table level โ€” can't do it earlier because the kernel was patching its own code (alternatives, jump labels).
  • pti_finalize() finalizes Page Table Isolation โ€” same reason.
  • free_initmem() reclaims __init-tagged functions โ€” has to be after every __init function has finished running.

What's reusable from the model

The six-phase shape is specific to Linux, but two general patterns survive in any boot path:

  1. Per-phase capability envelope. Each phase has a different "what can we call?" set. Documenting that envelope is the most useful thing a boot map can do.
  2. Allocator handoff as a discrete event. A primitive bootstrap allocator gives way to the real one at a specific moment. Phases before that moment can only use raw chunks; phases after can use objects.

Cross-references