# Linux boot phases

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

- [[linux-kernel-startup]] — the article this model came from
- [[kernel-self-patching]] — the runtime-rewriting that happens in Phase 4
- [[linux-preemption-models]] — what happens to scheduling after boot
