Boot Process
From BIOS POST to kernel initialization and user space startup — understanding how your operating system comes to life.
Boot Process
Every time you press the power button and watch your computer spring to life, a carefully orchestrated sequence of events unfolds
Introduction
Production Failure Scenarios
Scenario 1: Corrupted Bootloader
What happens: GRUB fails to load or is corrupted, often due to failed OS updates, disk errors, or dual-boot configuration issues. The system may show “GRUB rescue” prompt or fail to find the operating system.
Detection:
- System displays “No bootable device” or GRUB rescue prompt
- Boot menu disappears immediately without loading OS
- Error messages about missing files in /boot
Mitigation:
# Boot from live USB, then:
# For BIOS systems:
grub-install /dev/sda
update-grub
# For UEFI systems:
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=GRUB
update-grub
# Verify GRUB config exists
cat /boot/grub/grub.cfg
Scenario 2: Kernel Panic on Boot
What happens: The kernel fails during initialization, displaying “Kernel panic - not syncing” with a stack trace. Common causes include missing filesystem drivers, incompatible hardware, or corrupted initramfs.
Detection:
- Kernel panic message on screen
- System fails to reach login prompt
- Kernel version mismatch between initramfs and loaded kernel
Mitigation:
- Boot from previous kernel via GRUB menu (hold Shift during boot on BIOS, or Esc on UEFI)
- Regenerate initramfs if corrupted:
update-initramfs -u - Check for hardware compatibility issues
- Disable problematic kernel modules via boot parameters
Scenario 3: Systemd Service Failure Loop
What happens: A critical service fails to start but systemd retries endlessly, creating a boot loop. Common culprits include failed filesystem mounts, network configuration, or service dependency cycles.
Mitigation:
# Boot into rescue mode via kernel parameter: systemd.unit=rescue.target
# Or emergency mode: systemd.unit=emergency.target
# Check failed services
systemctl --failed
# View service logs
journalctl -u failed-service-name -b
# Check dependency tree
systemctl list-dependencies critical-target
Trade-off Table
| Boot Method | Speed | Security | Flexibility | Compatibility |
|---|---|---|---|---|
| BIOS/MBR | Slower | None | Limited | Old hardware |
| UEFI | Faster | Secure Boot | High | Modern hardware |
| BIOS/CSM | Medium | None | High | Legacy + modern |
| iPXE network boot | Variable | Depends | High | Diskless setups |
| Init System | Parallel Startup | Resource Usage | Complexity | Legacy Support |
|---|---|---|---|---|
| SysV init | No | Minimal | Low | Excellent |
| systemd | Yes | Higher | High | Good |
| OpenRC | Partial | Moderate | Medium | Good |
| runit | Yes | Minimal | Low | Limited |
Implementation Snippets
Viewing Boot Messages (Linux)
#!/bin/bash
# Analyze recent boot messages
echo "=== Kernel Messages from Current Boot ==="
dmesg | head -50
echo -e "\n=== Boot Time Errors ==="
dmesg | grep -iE "error|failed|warning" | tail -20
echo -e "\n=== systemd Boot Analysis ==="
systemd-analyze time
systemd-analyze blame | head -20
echo -e "\n=== Failed Services ==="
systemctl --failed --no-pager
GRUB Configuration Analysis
#!/bin/bash
# Inspect current GRUB configuration
echo "=== GRUB Default Boot Entry ==="
grep "^menuentry" /boot/grub/grub.cfg | head -5
echo -e "\n=== Kernel Command Line Parameters ==="
grep "linux" /boot/grub/grub.cfg | head -3
echo -e "\n=== Bootloader Version ==="
grub-install --version 2>/dev/null || echo "GRUB version check not available"
Analyzing Boot Process Timing
#!/bin/bash
# Detailed boot performance analysis
echo "=== systemd-analyze critical-chain ==="
systemd-analyze critical-chain
echo -e "\n=== Top 10 Slowest Services ==="
systemd-analyze blame | head -10
echo -e "\n=== Kernel Boot Timing ==="
cat /proc/stat | grep btime
uptime --since
UEFI Boot Order Configuration
#!/bin/bash
# UEFI-specific boot management
echo "=== Current Boot Order ==="
efibootmgr -v
echo -e "\n=== EFI Boot Entries ==="
ls -la /boot/efi/EFI/
echo -e "\n=== Secure Boot Status ==="
mokutil --sb-state 2>/dev/null || echo "mokutil not available"
Observability Checklist
Boot-Time Monitoring
# Record boot timing for analysis
systemd-analyze dot > boot-graph.dot
# Save full journal from boot
journalctl -b -1 > previous-boot.log 2>&1
# Capture kernel parameters used
cat /proc/cmdline
# Check filesystem mount order
mount | grep -E "^/dev"
Boot Failure Debugging
# Boot with debug shell
# Add to kernel parameters: init=/bin/bash
# For systemd debug
# Add: systemd.log_level=debug
# Enable early debugging
# Add: earlyprintk=vga,keep
# Network-based debugging (for embedded)
# Add: netconsole=[port]@[dev],[dev]
Common Pitfalls / Anti-Patterns
Secure Boot
Secure Boot is a UEFI feature that verifies the bootloader and kernel are signed by trusted keys. This prevents:
- Bootkits that replace the bootloader
- Rootkits that load before the OS
- Malicious kernel modules
Managing Secure Boot:
# Check Secure Boot status
mokutil --sb-state
# Enroll new keys for custom kernels
mokutil --import /path/to/sign.der
Measured Boot
Beyond Secure Boot’s signature verification, Measured Boot (part of TPM) records each boot stage measurement into the TPM. This enables remote attestation—proving to a remote party that the system booted a known-good configuration.
Full Disk Encryption Interaction
Boot process modifications required for FDE (LUKS, BitLocker with TPM):
- Early userspace (initramfs) must unlock the encrypted disk
- TPM-based BitLocker stores the key in the TPM
- Dracut or similar generates the unlock hooks
- A password or TPM seal is required to unlock
Understanding these interactions is critical when debugging boot failures with encrypted disks.
Common Pitfalls / Anti-patterns
-
Updating GRUB but not running update-grub — Editing /boot/grub/grub.cfg manually is overwritten by update-grub. Always edit /etc/default/grub and run update-grub
-
Kernel parameter typos — A single character error in kernel parameters can prevent booting. Always verify parameters in GRUB menu before booting
-
Initramfs not regenerated after kernel updates — New kernel modules or drivers require initramfs regeneration:
update-initramfs -u -
UEFI boot entries pointing to deleted files — Removing an EFI file without cleaning the boot entry leaves orphan entries. Use
efibootmgr -b XXXX -Bto delete -
Secure Boot blocking third-party drivers — unsigned kernel modules won’t load with Secure Boot enabled. Sign modules or disable Secure Boot (with security tradeoffs)
-
Disk order changes after drive swap — Adding/removing drives can change /dev/sda to /dev/sdb, breaking boot. Use UUIDs or labels in /etc/fstab and kernel parameters
Quick Recap Checklist
- Boot process goes through: POST → Firmware → Bootloader → Kernel → Init → Login
- BIOS/MBR and UEFI/GPT represent two generations of firmware interfaces
- GRUB loads the kernel and optional initramfs, passes boot parameters
- The kernel initializes core subsystems: memory, interrupts, scheduling, VFS
- systemd (or init) starts user space services in dependency order
- Bootloader corruption requires recovery via live USB
- Kernel panics during boot often indicate module or filesystem driver issues
- UEFI Secure Boot verifies bootloader and kernel signatures
- Full disk encryption interacts with boot via early userspace hooks
- Boot time optimization requires measuring each phase with systemd-analyze
Interview Questions
BIOS (Basic Input/Output System) is legacy firmware from the IBM PC era (1980s) that operates in 16-bit real mode with severe limitations: it can only boot from MBR-formatted disks up to 2TB, requires a compatibility layer for modern operating systems, and provides minimal configuration interfaces.
UEFI (Unified Extensible Firmware Interface) is the modern replacement. It runs in 32 or 64-bit mode, supports GPT disks larger than 2TB, includes Secure Boot for boot-time verification, and provides a full API for boot-time services. UEFI stores bootloaders as files in a dedicated EFI System Partition (ESP) rather than embedding code in the MBR.
Most modern systems ship with UEFI, with BIOS compatibility (CSM) available for legacy boot scenarios. Linux and Windows support both, but UEFI is preferred for modern installations.
The bootloader's job is to bridge the gap between the firmware (which knows how to read from storage) and the operating system kernel (which needs to be loaded into memory with proper parameters). It provides several critical functions:
- Locates the kernel on the storage device
- Loads the kernel and optional initial ramdisk (initramfs) into memory
- Understands filesystem structure to find files
- Presents boot options (multiple kernels, recovery modes)
- Passes configuration parameters to the kernel
On Linux, GRUB is the standard bootloader. It works with both BIOS and UEFI systems, understands ext4, XFS, Btrfs and other filesystems, and provides an interactive menu for boot selection.
initramfs (initial RAM filesystem) is a compressed cpio archive loaded into RAM by the bootloader before the kernel takes over. It contains a minimal Linux environment with utilities needed to prepare the real root filesystem.
The problem it solves: the kernel needs a root filesystem to mount, but if the root filesystem requires special drivers (LUKS encryption, LVM, RAID, specific SATA controllers), those drivers might be on the root filesystem itself—a chicken-and-egg problem.
initramfs breaks this cycle by containing the necessary drivers to unlock encrypted volumes, assemble RAID arrays, activate LVM logical volumes, and mount the real root filesystem. Once mounted, the system "pivots root" from initramfs to the real filesystem and continues booting.
When the kernel finishes early initialization, it looks for the first userspace process to run—the first program that isn't part of the kernel. On systemd systems, the kernel executes systemd as PID 1 (the first user-space process).
systemd then begins its job of starting the system: it reads its configuration files (unit files), analyzes dependencies between services, and starts services in the correct order. Unlike old SysV init with sequential startup, systemd starts many services in parallel where dependencies allow, significantly speeding up boot time.
systemd doesn't just start services—it owns the entire user-space environment: it manages mounting filesystems (including /proc, /sys, /dev), configures hostname and locale, sets up D-Bus for inter-process communication, and eventually starts the login prompt or graphical display manager.
Secure Boot is a UEFI feature that establishes a chain of trust from hardware to bootloader to kernel. During boot, each component's signature is verified before execution:
First, the UEFI firmware contains built-in trusted keys (DB). When the system boots, it verifies the bootloader's signature against these keys. If valid, the bootloader runs. The bootloader then verifies the kernel's signature, and optionally, the kernel verifies kernel module signatures. Only signed code from trusted vendors executes.
For Linux users, this means: pre-built kernels from major distributions work out of the box (they're signed). Custom kernels or third-party drivers need to be signed with a key enrolled in the system's trusted database. This prevents bootkits and rootkits from installing before the OS, significantly improving security—but requires additional steps for custom configurations.
Most production Linux kernels are compressed (gzip, LZMA, or zstd) to reduce size. After GRUB loads the kernel into memory, the kernel header specifies the decompression entry point. The decompressor runs in real mode initially, sets up a minimal page table, decompresses the kernel into its final location in memory, then jumps to the kernel's actual entry point.
During decompression, the kernel relocates itself (if needed), parses boot parameters from the command line, and prepares the early pagetable. If the kernel was built with CONFIG_RANDOMIZE_BASE, ASLR is applied even at this early stage. The decompression console output ("Loading Linux kernel...") is often visible during boot.
GRUB 2 locates initramfs via the initrd kernel parameter specified in GRUB config (/boot/grub/grub.cfg). GRUB reads the initramfs cpio archive into memory and passes the memory address and size to the kernel via the boot parameters. The kernel stores this address and uses it during the early userspace handoff.
Modern kernels also support initramfs as an embedded cpio archive compiled into the kernel image (via CONFIG_INITRAMFS_SOURCE). In this case, no external file is needed—the initramfs is part of the kernel image itself.
pivot_root() is the syscall that changes the root filesystem. During boot, the kernel initially mounts the initramfs as the root filesystem. Initramfs's /init script (or the kernel's internal initramfs code) performs the critical setup: locating the real root filesystem, mounting it, then calling pivot_root() to make it the new root. The old root (initramfs) is unmounted and its memory freed.
This "pivot root" technique solves the chicken-and-egg problem: the kernel needs the root filesystem mounted to use it, but the root filesystem might need kernel drivers (LUKS, LVM, RAID) that require an initramfs to load first.
UEFI Runtime Services are a set of services provided by the firmware that persist even after an OS boots—_GetVariable(), SetVariable(), QueryVariableInfo(), etc. They allow the OS to read/write UEFI variables stored in NVRAM (like boot order, Secure Boot keys, and platform diagnostics). The kernel maps these services into a known virtual address via SetVirtualAddressMap().
After the kernel takes over, these services are still callable from kernel context via the efivarfs filesystem (mounted at /sys/firmware/efi/efivars). This is how tools like efibootmgr and mokutil modify boot settings—via efivarfs which wraps the UEFI runtime service calls.
Measured boot uses the TPM (Trusted Platform Module) to record cryptographic measurements of each boot component. Before executing each component (UEFI firmware → bootloader → kernel → initramfs), its measurement is extended into a TPM Platform Configuration Register (PCR). PCR extension is additive: new_PCR = SHA(old_PCR || new_measurement).
The resulting PCR values cannot be altered without detection—if any component is tampered with, its measurement differs, producing a different PCR value. Remote attestation protocols can query these PCRs and prove to a remote party what code was booted. This enables "measured boot" attestation: proving your system booted an approved chain of software.
systemd.unit=rescue.target and systemd.unit=emergency.target?emergency.target starts a minimal shell on the main console as soon as possible—it mounts the root filesystem read-only and starts minimal debugging. No other services run. It's the most minimal rescue mode.
rescue.target is more complete: it mounts the root filesystem read-write, starts system logger, configures the hostname and locale, and brings up the network. It then runs a shell on tty1. rescue.target is appropriate when you need the filesystem mounted and some services running to troubleshoot—but without the normal multi-user service stack.
dracut is the tool that builds the initramfs image on Red Hat-based systems (Fedora, RHEL, CentOS). It analyzes the kernel modules and dependencies needed to boot the system, includes those modules in a cpio archive, and adds the shell scripts for the boot process. Unlike older mkinitrd, dracut is hardware-agnostic and generates a minimal-but-complete initramfs.
The relationship: dracut produces the initramfs; the initramfs uses dracut-generated scripts to locate the real root, load necessary drivers (LUKS, LVM, network), and hand off to the real root filesystem. Regenerating initramfs (dracut -f) is necessary after kernel updates or driver changes.
Secure Boot establishes a chain of trust via cryptographic signature verification. Each component measured before execution: UEFI verifies the bootloader's signature against keys in the UEFI database (db). If the bootloader is signed by a trusted key, it loads. The bootloader then verifies the kernel's signature before transfer. The kernel optionally verifies kernel modules.
A rootkit that replaces the bootloader would have an invalid signature—UEFI refuses to execute it. This prevents pre-OS rootkits that modified the bootloader to hide themselves. However, Secure Boot only verifies signers; a compromised key enrolled in the trusted database bypasses the protection.
The EFI System Partition is a dedicated FAT32 partition (type GUID: C12A7328-F81F-11D2-BA4B-00A0C93EC93B) on a GPT disk. UEFI firmware reads bootloaders from this partition—bootloaders live as files under /EFI/vendor/. UEFI boot managers also store boot entries (pointers to these files) in NVRAM.
The ESP can also store EFI variables (when efivarfs isn't mounted), firmware update capsules, and diagnostic tools. It uses FAT32 for maximum compatibility across UEFI implementations. Some systems use a separate "MDT" partition for tool extensions, but the standard ESP is a single FAT32 partition.
PID 1 is the first user-space process the kernel starts after initialization. In traditional SysV init, this is the init binary. In systemd, it's the systemd binary. PID 1 is special because it inherits orphaned processes (if a parent dies, its children are reparented to PID 1) and receives signals that would otherwise kill the system (e.g., SIGTERM for graceful shutdown).
The kernel waits for PID 1 to exec the specified userspace program before considered "booted." If PID 1 dies, the kernel panics (cannot proceed without an init process). PID 1 also manages service lifecycle, mount propagation (shared mounts become slave), and is the ancestor of all subsequent processes.
systemd-analyze blame help identify boot bottlenecks?systemd-analyze blame prints a list of units sorted by how long they took to activate during the most recent boot. This reveals which services are the slowest to start. For example, if network-online.target takes 8 seconds, you know the network configuration or DHCP is slow. The output shows the accumulated time spent in each unit's activation.
Use it with systemd-analyze critical-chain to see the critical path (the longest chain of dependent units)—optimizing units on the critical path has the most impact on total boot time.
Loading at boot time (via /etc/modules or module configuration in initramfs): modules load during the initramfs phase or early boot, before the filesystem is fully available. This is necessary for modules required to mount the root filesystem (storage controllers, filesystem drivers, encryption). The order is critical.
Loading after boot (via modprobe or insmod): the system is fully operational, so module dependencies can be resolved from /lib/modules/$(uname -r)/modules.dep, and user-space is available for error handling. modprobe also handles module arguments via /etc/modprobe.d/ configuration files.
/run and how does it differ from /var in early boot?/run is a tmpfs filesystem mounted early in boot (by systemd or init scripts) to hold runtime data that does not need to persist across boots—PID files, socket files, transient application state. It replaces the older /var/run symlink and /var/lock symlink patterns.
The key difference: /var may reside on the persistent root filesystem, which may not be mounted yet during early boot. /run is a RAM-based tmpfs, so it is available immediately after the early boot mount manager starts. Services needing runtime files during early boot use /run, not /var.
This error occurs when the kernel's early userspace (/init or the shell from initramfs) cannot open /dev/console for standard input. The kernel passes console= boot parameters to set the console device, but if that device node doesn't exist in initramfs, the open fails.
Resolution: rebuild initramfs with the correct device nodes (udev or mdev populates /dev), or pass a console= device that exists in the initramfs. In dracut-based systems, dracut --force --add udev ensures udev runs early enough to create /dev/console.
In PXE boot, the firmware broadcasts a DHCP request with PXE options. The DHCP server returns boot server address and filename (the bootloader). The firmware downloads the bootloader via TFTP and executes it. GRUB (compiled as a PXE GRUB binary) then uses its own config or a network path to locate the kernel and initramfs via TFTP/HTTP, passes kernel parameters (including ip=dhcp), and boots.
The initramfs must include network drivers to bring up the network after boot. The PXE boot flow bypasses local storage entirely—the entire boot chain (bootloader, kernel, initramfs) comes from the network. This is standard in diskless workstations, cloud instances (where metadata comes via DHCP/PXE), and enterprise provisioning systems.
Further Reading
- Kernel Architecture — How the kernel is structured internally
- System Calls Interface — How user programs invoke kernel services
- Memory Management — Virtual memory and kernel memory management
- Red Hat Article: Understanding Systemd
- Arch Wiki: Boot Process
Conclusion
The boot process reveals how an operating system progressively takes control of hardware—from the moment you press the power button to the moment you see a login prompt. Each phase has its own failure modes, recovery mechanisms, and security implications.
Understanding boot internals becomes practical when systems fail to start, when you need to enable Secure Boot for compliance, or when you’re building embedded systems with custom bootloaders. The concepts transfer directly to troubleshooting initramfs failures, configuring GRUB for encrypted disks, or implementing secure boot chains.
For continued learning, explore kernel initialization internals, systemd unit file authoring, and early-userspace debugging techniques. These build directly on the boot concepts covered here and help you become proficient at recovering from boot failures and optimizing startup time.
Category
Related Posts
ASLR & Stack Protection
Address Space Layout Randomization, stack canaries, and exploit mitigation techniques
Assembly Language Basics: Writing Code the CPU Understands
Learn to read and write simple programs in x86 and ARM assembly, understanding registers, instructions, and the art of thinking in low-level operations.
Boolean Logic & Gates
Understanding AND, OR, NOT gates and how they combine into arithmetic logic units — the building blocks of every processor.