The Linux Boot Process

From power-on to a running userspace — firmware, bootloader, kernel initialization, and init. x86-64 and UEFI-first, with BIOS/MBR notes where relevant.

01FIRMWARE
02BOOTLOADER
03KERNEL IMG
04KERNEL INIT
05INITRAMFS
06INIT
01

Firmware & Power-On

When the CPU is released from reset, it begins executing at a fixed physical address. On x86-64, this is 0xFFFFFFF0 — a 16-byte "reset vector" that immediately jumps into the firmware ROM.

UEFI

Modern systems use UEFI. Execution proceeds through three phases before the OS loader is invoked:

SEC (Security) Runs entirely in cache-as-RAM (CAR). Validates the PEI image, establishes a minimal trusted environment.
PEI (Pre-EFI Init) Initializes DRAM, chipset basics, and any silicon-specific code. Produces a Hand-Off Block (HOB) list passed forward.
DXE (Driver Execution) Full UEFI environment. Loads drivers from the firmware volume, brings up PCIe, storage, and USB. Produces the EFI System Table visible to later stages.
BDS (Boot Device Select) Evaluates the NVRAM boot order, locates an EFI application (\EFI\BOOT\BOOTX64.EFI or a distro-specific path), and transfers control to it.
Secure Boot
Before executing any EFI binary, UEFI verifies its signature against the db allowlist. Shim (shimx64.efi) is the standard first-stage for distros — it carries its own signature accepted by Microsoft's keys and then verifies the bootloader with its own MOK database.

Legacy BIOS / MBR (reference)

BIOS loads the first 512 bytes of the boot disk (MBR) at physical address 0x7C00 and jumps to it. The MBR code locates a bootable partition via the partition table and loads a second-stage bootloader. All of this happens in 16-bit real mode with no hardware abstraction.

02

Bootloader

The dominant bootloader on x86-64 Linux is GRUB2. The EFI binary grubx64.efi is loaded directly by BDS as an EFI application; it runs in 64-bit EFI mode with full access to UEFI Boot Services.

GRUB2 tasks

Linux Boot Protocol

GRUB places a struct boot_params at a known address and passes it to the kernel. It contains memory map (E820 table), command-line pointer, initrd address and size, video mode info, and EFI-specific data.

boot_params layout (partial)
0x000 screen_info
0x040 apm_bios_info
0x090 hdr          ← setup_header (magic 0xAA55, protocol version, etc.)
0x1E8 e820_entries
0x2D0 e820_table[128]
...   efi_info     ← EFI memory map, system table pointer
systemd-boot / EFI stub
An alternative to GRUB: the kernel itself can be a valid EFI application via CONFIG_EFI_STUB. systemd-boot (or any EFI loader) loads vmlinuz directly as an EFI binary, passing parameters via EFI LoadOptions. No separate bootloader code path needed.
03

Kernel Load & Decompression

The file on disk is vmlinuz — a self-decompressing archive. The name derives from virtual memory Linux; the z suffix indicates compression (historically gzip, now commonly zstd or lz4 depending on distro config).

Image structure

bzImage header First ~512 bytes. Contains the setup_header with magic number 0x53726448 ("HdrS"), protocol version, load address hints, and payload offset/size.
Real-mode setup code Historically ran in 16-bit mode; with the boot protocol ≥ 2.02 and a modern bootloader, this is bypassed entirely — the bootloader uses the 32/64-bit entry points directly.
Compressed payload The actual kernel binary, compressed. Preceded by a small decompressor stub.

Decompression

Control passes to the decompressor stub at arch/x86/boot/compressed/head_64.S. It sets up a minimal page table, switches to long mode if not already in it, decompresses the kernel image into memory (usually at 0x1000000), and jumps to the uncompressed kernel entry — startup_64 in arch/x86/kernel/head_64.S.

KASLR
With CONFIG_RANDOMIZE_BASE, the decompressor calls into the firmware to get a random physical offset before placing the kernel. The final load address is stored and used to fix up any absolute references before jumping into the kernel proper.
04

Early Kernel Initialization

Once at startup_64, the kernel is running its own code with no OS beneath it. The hardware state is still largely as the firmware/bootloader left it.

startup_64 → start_kernel

startup_64 Sets up early page tables (identity + kernel mappings). Clears BSS. Calls x86_64_start_kernel().
x86_64_start_kernel Fixes up CR3 to the real kernel PGD. Calls into start_kernel() in init/main.c.
start_kernel() Main C entry point. Runs hundreds of init calls in a specific order. Never returns.

Key initialization sequence inside start_kernel()

SMP bring-up

After the BSP (Bootstrap Processor) completes start_kernel(), it sends INIT-SIPI-SIPI sequences via the LAPIC to wake Application Processors. Each AP runs start_secondary(), initializes its own per-CPU state, and joins the scheduler runqueue. CPU hotplug controls this process on modern kernels.

Verify CPU topology at runtime
# logical CPUs and mapping
$ lscpu --extended

# scheduler domains (NUMA/cache topology seen by the kernel)
$ cat /sys/kernel/debug/sched/domains/cpu0/domain0/name
05

initramfs

The kernel cannot mount the real root filesystem without drivers that may themselves be kernel modules stored on that filesystem (disk controller, LUKS, LVM, network). initramfs breaks this chicken-and-egg problem.

What it is

A gzip/lz4/zstd-compressed CPIO archive passed to the kernel by the bootloader via the initrd_start / initrd_size fields in boot_params. The kernel unpacks it into a tmpfs instance and mounts it as the initial root (/).

initrd vs initramfs
The older initrd was a block-device image mounted as a ramdisk (required a filesystem driver and a fixed size). initramfs is a CPIO archive unpacked directly into tmpfs — cheaper, no fixed size, no extra filesystem layer. Modern kernels use initramfs; the term "initrd" is colloquially reused for both.

Contents and execution

The archive contains a minimal root filesystem sufficient to locate and mount the real root:

Pivot to real root

Once the real root device is mounted (typically at /sysroot), the initramfs init process calls switch_root /sysroot /sbin/init — a syscall sequence that moves the mount, chroots, and execs the final init binary. The initramfs tmpfs is freed.

Inspect or unpack an initramfs
# list contents
$ lsinitrd /boot/initramfs-$(uname -r).img   # dracut/Fedora
$ lsinitcpio /boot/initramfs-linux.img        # mkinitcpio/Arch

# manual unpack (adjust for compression type)
$ mkdir /tmp/ir && cd /tmp/ir
$ file /boot/initramfs-$(uname -r).img        # check compression
$ zstdcat /boot/initramfs-$(uname -r).img | cpio -idm
06

Init System

PID 1 in the final rootfs is the init system. It is the ancestor of all userspace processes and must never exit. On the vast majority of current Linux distributions, this is systemd.

systemd boot sequence

default.target Typically symlinked to graphical.target or multi-user.target. Defines the set of units that must be active.
Unit graph systemd builds a dependency graph from all unit files in /lib/systemd/system/ and /etc/systemd/system/. Edges are Requires=, Wants=, Before=, After=.
Parallel activation Units with satisfied dependencies are activated concurrently. D-Bus socket activation and device units (.device via udev) allow lazy-start of services.
Early targets sysinit.targetbasic.targetnetwork.targetmulti-user.target / graphical.target. Each is a synchronization point.

Key early units

Examining boot performance

Diagnostics
# Overall boot time breakdown
$ systemd-analyze

# Per-unit timing (slowest first)
$ systemd-analyze blame

# Critical path through the dependency graph
$ systemd-analyze critical-chain

# Full unit dependency graph (renders with dot)
$ systemd-analyze dot | dot -Tsvg > boot.svg

# Kernel ring buffer from this boot
$ journalctl -k -b 0

# All messages from initramfs phase
$ journalctl -b 0 -o short-monotonic | head -200
Alternative init systems
SysVinit (sequential, shell scripts, still found on some minimal/embedded systems), OpenRC (dependency-based, shell-based, default on Gentoo and Alpine), runit (used on Void Linux — supervision-based, no unit files). The kernel only cares that PID 1 stays alive and reaps orphaned processes; the rest is policy.

Quick Reference

Key files and paths

/boot/vmlinuz-*Compressed kernel image loaded by the bootloader.
/boot/initramfs-*.imginitramfs CPIO archive (dracut naming; initramfs-linux.img on Arch).
/boot/grub/grub.cfgGRUB2 generated configuration. Do not edit directly; regenerate with grub-mkconfig.
/proc/cmdlineKernel command line as passed by the bootloader.
/proc/1/exeSymlink to the init binary (confirms what PID 1 is).
/sys/firmware/efiPresent only on UEFI boots. Contains EFI variables, runtime services info.
/sys/firmware/acpiACPI tables exposed by the kernel. acpidump extracts them.
/sys/kernel/boot_paramsThe raw boot_params struct as seen by the kernel (available with CONFIG_BOOT_CONFIG).
/etc/systemd/system/Local unit files and overrides (higher priority than /lib/systemd/system/).
/etc/fstabFilesystem mount table used by systemd-fstab-generator to create mount units.

Useful kernel parameters

root=Root device. Accepts /dev/sdXN, UUID=, PARTUUID=, LABEL=.
rootflags=Mount options for the root filesystem.
init=Override PID 1. E.g. init=/bin/bash for emergency shell.
rd.breakDrop to a shell inside the initramfs before switch_root (dracut).
systemd.unit=Boot to a specific target, e.g. rescue.target or emergency.target.
nomodesetDisables KMS; kernel stays in VESA/EFI framebuffer mode. Useful for GPU driver debugging.
iommu=offDisables IOMMU. Relevant when debugging DMA issues.
loglevel=Early printk verbosity (0–7). Default is typically 4 (KERN_WARNING).
earlyprintk=Direct early kernel output to a serial port or EFI console before the framebuffer is up.