The Boot Sequence
InstanceStart is the moment a configured-but-dormant Firecracker process becomes a running microVM. One
PUT to /actions flows through the action channel to the
PrebootApiController, which calls the builder. From there a precise sequence runs: allocate guest
memory, load the kernel ELF into it, write the boot parameters the kernel expects (the "zero page" with
the e820 map, the command line, the initrd pointer), set each vCPU's registers so it boots straight into
64-bit long mode, create the devices, and finally release the vCPU threads into their KVM_RUN loops.
The order is not arbitrary — each step depends on the previous one. This chapter walks that sequence in
builder.rs and arch/, explains why each step is where it is, and contrasts the aarch64 path (no
zero page; an FDT instead).
After this chapter you will be able to: trace InstanceStart from the action to running guest code; name
the artifacts of the boot protocol (zero page, e820, cmdline, initial regs) and where they are written;
explain why an x86 vCPU needs long-mode setup before its first KVM_RUN; and describe how aarch64 differs.
Note: Firecracker loads an uncompressed
vmlinuxELF and jumps toe_entryin 64-bit long mode — no BIOS, no bootloader, no real-mode trampoline on x86. That is a major reason microVMs boot in tens of milliseconds. The cost is that Firecracker itself must do everything a firmware/bootloader normally does: set up page tables, the GDT, long mode, and the boot params.
The trigger: InstanceStart → the builder
# Find the builder entry points called when the microVM starts.
rg -n "build_microvm_for_boot|build_and_boot_microvm|build_microvm_from_snapshot|StartMicroVm" src/vmm/src/builder.rs src/vmm/src/rpc_interface.rs
PrebootApiController handles StartMicroVm by invoking the builder with the accumulated VmResources.
The builder is the choreographer of every step below. Read it top to bottom — it is the single best map
of the boot path.
rg -n "fn build_microvm_for_boot" -A 80 src/vmm/src/builder.rs
The sequence (x86_64)
flowchart TD
A["InstanceStart -> build_microvm_for_boot(VmResources)"] --> B["create guest memory (mmap + KVM_SET_USER_MEMORY_REGION)"]
B --> C["create VM fd, irqchip (KVM_CREATE_IRQCHIP), vCPUs (KVM_CREATE_VCPU)"]
C --> D["load vmlinux ELF into guest memory (linux-loader Elf)"]
D --> E["load initrd if any"]
E --> F["write boot_params / zero page: e820, cmdline ptr, initrd ptr"]
F --> G["configure CPUID/MSRs per CPU template (SET_CPUID2)"]
G --> H["set vCPU regs/sregs: long mode, rip=e_entry, rsi=ZERO_PAGE"]
H --> I["create + register devices (block/net/vsock/serial/...) on the buses"]
I --> J["advertise devices to guest (cmdline virtio_mmio.device=... / ACPI)"]
J --> K["start vCPU threads; each enters KVM_RUN"]
Walk it with the table; locate each step with the rg beside it.
| Step | What happens | Locate it |
|---|---|---|
| 1. Memory | mmap host RAM, register with KVM | rg -n "create_guest_memory|set_user_memory_region" src/vmm/src/builder.rs src/vmm/src/vstate/ |
| 2. VM + irqchip + vCPUs | Create VM fd, the in-kernel irqchip, one vCPU fd per CPU | rg -n "create_irq_chip|setup_irqchip|create_vcpu|Vm::new" src/vmm/src/ |
| 3. Load kernel | Parse the ELF, copy PT_LOAD segments to guest RAM, record e_entry | rg -n "Elf|load_kernel|kernel_entry|KernelLoader" src/vmm/src/ |
| 4. Load initrd | If configured, copy initrd image; record its guest addr/size | rg -n "initrd|load_initrd" src/vmm/src/ |
| 5. Boot params | Build the zero page: e820 map, cmdline pointer, initrd ptr | rg -n "configure_system|bootparam|zero_page|build_bootparams|cmdline" src/vmm/src/arch/x86_64/ |
| 6. CPUID/MSRs | Apply the CPU template; SET_CPUID2 | cpu-templates-and-cpuid.md |
| 7. vCPU regs | Set long mode and initial register values | rg -n "setup_regs|setup_sregs|configure|long mode|EFER|CR0" src/vmm/src/arch/x86_64/ |
| 8. Devices | Build, register on the MMIO/PIO buses | the-mmio-bus-and-device-manager.md |
| 9. Advertise | Tell the guest where the devices are (cmdline / ACPI) | virtio-transport-mmio.md, acpi-and-mptable.md |
| 10. Start vCPUs | Release the vCPU threads into KVM_RUN | vcpu-run-loop-and-vm-exits.md |
The memory layout and why constants live where they do
x86 Linux boot has a fixed protocol, and Firecracker mirrors it with layout constants:
rg -n "ZERO_PAGE_START|CMDLINE_START|HIMEM_START|BOOT_STACK|GDT|PML4|layout" src/vmm/src/arch/x86_64/layout.rs
| Constant (verify on your branch) | Typical value | Role |
|---|---|---|
ZERO_PAGE_START | 0x7000 | The boot_params struct the kernel reads at entry |
CMDLINE_START | 0x20000 | The kernel command-line string |
HIMEM_START | 0x100000 (1 MiB) | Where the kernel image is loaded |
| boot stack / page tables / GDT | low addresses | Set up by Firecracker for the long-mode jump |
| MMIO gap | below 4 GiB | Reserved for device MMIO windows; RAM resumes above 4 GiB |
The zero page (boot_params) is the data structure the Linux x86 boot protocol mandates. Firecracker
fills it via linux-loader's bootparam types and a LinuxBootConfigurator: the e820 memory map
(which physical ranges are RAM vs reserved — the MMIO gap is a hole), the pointer to the command line at
CMDLINE_START, and the initrd location/size. The kernel reads all of this from the address Firecracker
puts in rsi.
rg -n "configure_system|LinuxBootConfigurator|add_e820_entry|E820|setup_header" src/vmm/src/arch/x86_64/
Why long mode before the first KVM_RUN
A fresh x86 vCPU is in 16-bit real mode (see kvm-fundamentals.md). A vmlinux
ELF's entry point expects 64-bit long mode with identity-mapped low memory. So before releasing the
vCPU, Firecracker sets, via KVM_SET_SREGS/KVM_SET_REGS:
| Register state | Why |
|---|---|
cr0 = PE | PG | protected mode + paging on |
cr4 = PAE | physical address extension (required for long mode) |
efer = LME | LMA | long mode enabled + active |
cr3 → identity page tables (first ~1 GiB) | so virtual == physical for the early kernel |
| GDT with 64-bit code/data segments | a valid long-mode segment setup |
rip = e_entry | the kernel's ELF entry point |
rsi = ZERO_PAGE_START | the kernel reads boot_params from here |
rg -n "setup_regs|setup_sregs|setup_page_tables|setup_gdt|EFER_LME|EFER_LMA|CR0_PE|CR4_PAE" src/vmm/src/arch/x86_64/
Get any of these wrong and the very first KVM_RUN returns KVM_EXIT_FAIL_ENTRY — the CPU could not even
enter the guest. This is the single most common "guest never boots" failure for hand-rolled VMMs, which is
why Lab 1.4 makes you do it yourself first.
The aarch64 contrast
aarch64 has no zero page and no e820. Instead, Firecracker builds a Flattened Device Tree (FDT/DTB) —
a binary blob describing CPUs, memory, the GIC interrupt controller, and the virtio-mmio devices — using
rust-vmm's vm-fdt (FdtWriter). It loads an arm64 PE Image kernel, writes the FDT into guest RAM,
and passes the FDT's address in x0.
rg -n "FdtWriter|vm-fdt|create_fdt|DRAM_MEM_START|GIC|PE\b|aarch64" src/vmm/src/arch/aarch64/
| x86_64 | aarch64 | |
|---|---|---|
| Kernel format | uncompressed vmlinux ELF | arm64 PE Image |
| Hardware description | zero page + e820 (boot_params) | FDT/DTB (device tree) |
| Passed to kernel via | rsi → ZERO_PAGE_START | x0 → FDT address |
| Interrupt controller | PIC/IOAPIC/LAPIC | GIC |
| RAM base | low + above-4 GiB high RAM | DRAM_MEM_START = 0x8000_0000 |
| CPU topology advertised by | MPTable / ACPI | FDT nodes |
The two paths converge again at "create devices, advertise them, start the vCPUs." The architecture
difference is concentrated in arch/{x86_64,aarch64}/; the builder selects the right one. The default
kernel command line is roughly reboot=k panic=1 pci=off nomodule 8250.nr_uarts=0 plus console=ttyS0
(varies — verify).
Reading exercise
# 1. The builder, top to bottom.
rg -n "fn build_microvm_for_boot" -A 100 src/vmm/src/builder.rs
# 2. Kernel load + entry.
rg -n "Elf|load_kernel|kernel_entry|HIMEM_START" src/vmm/src/ src/vmm/src/arch/x86_64/
# 3. Boot params / zero page.
rg -n "configure_system|LinuxBootConfigurator|E820|cmdline|ZERO_PAGE_START" src/vmm/src/arch/x86_64/
# 4. Long-mode register setup.
rg -n "setup_regs|setup_sregs|EFER_LME|CR0_PE|CR4_PAE|e_entry|rsi" src/vmm/src/arch/x86_64/
# 5. aarch64 FDT.
rg -n "FdtWriter|create_fdt|DRAM_MEM_START|x0" src/vmm/src/arch/aarch64/
# 6. Boot a real microVM and watch the kernel come up on the serial console (Lab 1.3).
Answer:
- List the boot steps in order from
InstanceStartto running guest code, and say why memory must come before kernel load and kernel load before boot params. - What is the zero page, what three things does Firecracker put in it, and how does the kernel find it?
- What register state makes an x86 vCPU boot in long mode, and what happens at the first
KVM_RUNif it is wrong? - Why does loading the kernel require recording
e_entry, and where does it end up? - How does aarch64 describe hardware to the kernel instead of a zero page, and which register carries it?
- Where in the tree do the x86 vs aarch64 boot differences live, and what selects between them?
Common bugs and symptoms
| Symptom | Root cause | Where to look |
|---|---|---|
First KVM_RUN → KVM_EXIT_FAIL_ENTRY, guest never boots | Long-mode sregs/regs wrong | setup_sregs/setup_regs in arch/x86_64/ |
| Kernel panics "no memory" / wrong RAM size | e820 map wrong; RAM/MMIO-gap overlap | configure_system, layout constants |
| Kernel boots but no console output | cmdline console= wrong, or serial device not registered/advertised | cmdline build; serial-console-and-legacy-devices.md |
| Guest can't find root device | virtio-mmio device not advertised on the cmdline / FDT | device advertise step; virtio-transport-mmio.md |
| initrd ignored | initrd pointer/size not written to boot params | load_initrd; boot params |
| aarch64 guest hangs at entry | FDT malformed or wrong address in x0 | create_fdt; vm-fdt usage |
Validation: prove you understand this
- Draw the boot sequence from
InstanceStarttoKVM_RUN, labelling each step's dependency on the prior. - Explain the zero page: its contents, who reads it, and how its address reaches the kernel.
- State the exact register/control-bit setup for long mode and the failure mode if it's wrong.
- Explain why Firecracker needs no BIOS/bootloader and what it does instead.
- Contrast x86_64 and aarch64 boot: kernel format, hardware description, and the register that carries it.
- Name three things that go wrong if the device-advertisement step is skipped.
Next: CPU Templates and CPUID — step 6 of the boot sequence, in depth.