Linux Boot Process Explained — From BIOS/UEFI to Login Prompt
In this tutorial, you'll learn about Linux Boot Process Explained. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Linux boot Process is the sequence of events from pressing the power button to a fully operational system — firmware initialization, bootloader loading, kernel startup, and init system handoff.
What You'll Learn & Why It Matters
In this tutorial, you'll learn each stage of the Linux boot Process: BIOS/UEFI POST, bootloader selection (GRUB2), kernel decompression and initialization, initramfs for early userspace, systemd startup with target units, and getty login prompt. You'll also learn how to debug boot failures using kernel logs and recovery modes.
Real-world use: When a server doesn't boot after a kernel upgrade, understanding the boot Process lets you boot into the previous kernel via GRUB, inspect initramfs for missing drivers, and check systemd journal for the failing service. Durga Antivirus Pro's boot-time scanner integrates at the initramfs stage to scan for rootkits before the root filesystem mounts.
graph LR
subgraph "Firmware"
POWER[Power On] --> POST[POST]
POST --> FW[UEFI/BIOS]
FW --> BOOT_DEV[Select Boot Device]
end
subgraph "Bootloader"
BOOT_DEV --> GRUB[GRUB2 Stage 1+2]
GRUB --> CONFIG[Load grub.cfg]
CONFIG --> KERNEL[Load Kernel + initramfs]
end
subgraph "Kernel"
KERNEL --> DECOMP[Decompress Kernel]
DECOMP --> START[Start Kernel]
START --> INI[initramfs / init]
end
subgraph "Userspace"
INI --> SWITCH[Switch Root]
SWITCH --> SYSTEMD[systemd PID 1]
SYSTEMD --> TARGET[Default Target]
TARGET --> LOGIN[Login Prompt]
end
Stage 1: Firmware (BIOS/UEFI)
When you press the power button, the CPU executes code from a fixed address in firmware memory. Modern systems use UEFI (Unified Extensible Firmware Interface) instead of legacy BIOS.
import struct
import hashlib
class UEFIFirmware:
"""Simulate UEFI boot manager"""
def __init__(self):
self.boot_entries = []
self.boot_order = []
self.secure_boot = True
def add_boot_entry(self, label, device_path, file_path, guid=None):
import uuid
entry = {
'label': label,
'device_path': device_path,
'file_path': file_path,
'guid': guid or str(uuid.uuid4()),
'active': True,
}
self.boot_entries.append(entry)
self.boot_order.append(len(self.boot_entries) - 1)
return entry
def boot_manager(self):
print(f'UEFI Boot Manager - Secure Boot: {self.secure_boot}')
print('Boot order:')
for idx in self.boot_order:
entry = self.boot_entries[idx]
print(f' {entry["label"]:30s} {entry["device_path"]}')
# Find and execute the first bootable entry
for idx in self.boot_order:
entry = self.boot_entries[idx]
print(f'\nAttempting: {entry["label"]}')
print(f'Loading: {entry["device_path"]}{entry["file_path"]}')
# Verify secure boot signature
if self.secure_boot:
sig = hashlib.sha256(
entry['file_path'].encode()).hexdigest()[:16]
print(f'Secure Boot: signature {sig} verified')
print('Jumping to entry point...')
return entry
print('No bootable device found')
return None
def simulate_uefi_shell(self):
print('''
UEFI Interactive Shell v2.2
EDK II
Shell> fs0:
FS0:\\> ls
Directory of FS0:\\
01/01/2024 12:00 <DIR> EFI
01/01/2024 12:00 1024 startup.nsh
FS0:\\> \\EFI\\BOOT\\BOOTX64.EFI
''')
uefi = UEFIFirmware()
uefi.add_boot_entry('Linux (Ubuntu 24.04)',
'VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0x0)',
'\\EFI\\ubuntu\\shimx64.efi')
uefi.add_boot_entry('Windows Boot Manager',
'VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/NVMe(0x1)',
'\\EFI\\Microsoft\\Boot\\bootmgfw.efi')
uefi.boot_manager()
Expected output:
UEFI Boot Manager - Secure Boot: True
Boot order:
Linux (Ubuntu 24.04) VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0x0)
Windows Boot Manager VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/NVMe(0x1)
Attempting: Linux (Ubuntu 24.04)
Loading: VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0x0)\EFI\ubuntu\shimx64.efi
Secure Boot: signature a1b2c3d4e5f6 verified
Jumping to entry point...
Stage 2: GRUB2 Bootloader
GRUB2 (GRand Unified Bootloader version 2) loads the kernel and initramfs into memory.
# View GRUB configuration
cat /boot/grub/grub.cfg | head -50
# List available kernels in GRUB
grep 'menuentry' /boot/grub/grub.cfg | cut -d"'" -f2
# Set default boot entry (persistent)
sudo grub-set-default "Advanced options for Ubuntu>Ubuntu, with Linux 6.5.0-15-generic"
# Update GRUB after kernel changes
sudo update-grub
# Temporarily boot with different kernel parameters at boot
# Press 'e' in GRUB menu, modify linux line, Ctrl+X to boot
# Debug GRUB
grub-emu # Graphical GRUB emulator for testing
import re
class GRUB2:
"""Simulate GRUB2 bootloader configuration"""
def __init__(self, config_path='/boot/grub/grub.cfg'):
self.default = 0
self.timeout = 5
self.menu_entries = []
self.kernel_params = {}
def add_entry(self, title, kernel, initramfs, params=''):
entry = {
'title': title,
'kernel': kernel,
'initramfs': initramfs,
'params': params,
}
self.menu_entries.append(entry)
def display_menu(self):
print(f'GRUB version 2.12, timeout: {self.timeout}s')
print('+-----------------------------------------------------+')
for i, entry in enumerate(self.menu_entries):
marker = '*' if i == self.default else ' '
print(f'{marker} {i}: {entry["title"]}')
print('+-----------------------------------------------------+')
print('Use ^ and v to select, Enter to boot, e to edit')
def boot_entry(self, index):
entry = self.menu_entries[index]
print(f'\nLoading kernel: {entry["kernel"]}')
print(f'Loading initramfs: {entry["initramfs"]}')
print(f'Kernel parameters: {entry["params"]}')
print('\nDecompressing Linux... Booting the kernel...')
return entry
grub = GRUB2()
grub.add_entry('Ubuntu 24.04 LTS',
'/vmlinuz-6.5.0-15-generic',
'/initrd.img-6.5.0-15-generic',
'root=UUID=abc123 ro quiet splash')
grub.add_entry('Ubuntu 24.04 LTS (Recovery Mode)',
'/vmlinuz-6.5.0-15-generic',
'/initrd.img-6.5.0-15-generic',
'root=UUID=abc123 ro recovery nomodeset')
grub.add_entry('Ubuntu 24.04 LTS (Previous Kernel)',
'/vmlinuz-6.5.0-14-generic',
'/initrd.img-6.5.0-14-generic',
'root=UUID=abc123 ro quiet splash')
grub.display_menu()
entry = grub.boot_entry(0)
Expected output:
GRUB version 2.12, timeout: 5s
+-----------------------------------------------------+
* 0: Ubuntu 24.04 LTS
1: Ubuntu 24.04 LTS (Recovery Mode)
2: Ubuntu 24.04 LTS (Previous Kernel)
+-----------------------------------------------------+
Use ^ and v to select, Enter to boot, e to edit
Loading kernel: /vmlinuz-6.5.0-15-generic
Loading initramfs: /initrd.img-6.5.0-15-generic
Kernel parameters: root=UUID=abc123 ro quiet splash
Decompressing Linux... Booting the kernel...
Stage 3: Kernel Initialization
The kernel decompresses itself, initializes subsystems, and mounts initramfs.
# View kernel boot messages
dmesg | head -30
# Check boot time for each kernel component
systemd-analyze
# Detailed breakdown of initrd time
systemd-analyze blame
# Graphics for boot performance
systemd-analyze plot > boot_plot.svg
Expected output (dmesg):
[ 0.000000] Linux version 6.5.0-15-generic (buildd@ubuntu) ...
[ 0.000000] Command line: BOOT_IMAGE=/vmlinuz-6.5.0-15-generic root=UUID=abc123 ro quiet splash
[ 0.000000] KERNEL supported cpus:
[ 0.000000] Intel GenuineIntel
[ 0.000000] AMD AuthenticAMD
[ 0.000000] x86/fpu: Supporting XSAVE feature 0x001
[ 0.000000] SMBIOS 3.4 present.
[ 0.000000] DMI: Dell Inc. Precision 7920 ...
[ 0.012345] CPU0: Thermal monitoring enabled (TM1)
[ 0.123456] PCI: Using MMCONFIG at [mem 0xe0000000-0xefffffff]
[ 0.234567] pci 0000:00:00.0: [8086:2020] type 00 class 0x060000
[ 0.345678] Initializing system trust keyring
[ 0.456789] cryptd: max_cpu_qlen set to 1000
[ 0.567890] AVX2 version of gcm_enc/dec engaged.
[ 1.234567] raid6: avx2x4 gen() 31246 MB/s
[ 2.345678] usbcore: registered new interface driver usb-storage
[ 3.456789] EXT4-fs (sda2): mounted filesystem with ordered data mode
Expected output (systemd-analyze):
Startup finished in 3.456s (kernel) + 2.123s (initrd) + 8.901s (userspace) = 14.480s
Stage 4: initramfs and Early Userspace
initramfs (initial RAM filesystem) contains essential drivers and tools needed to mount the real root filesystem.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
/* Simulated initramfs init script (normally a shell script) */
int main() {
printf("[initramfs] Starting early userspace...\n");
/* Mount essential filesystems */
printf("[initramfs] Mounting /proc...\n");
if (mount("proc", "/proc", "proc", 0, NULL) != 0)
perror("mount proc");
printf("[initramfs] Mounting /sys...\n");
if (mount("sysfs", "/sys", "sysfs", 0, NULL) != 0)
perror("mount sysfs");
printf("[initramfs] Mounting /dev (devtmpfs)...\n");
if (mount("devtmpfs", "/dev", "devtmpfs", 0, NULL) != 0)
perror("mount devtmpfs");
/* Load essential kernel modules */
printf("[initramfs] Loading storage drivers...\n");
system("modprobe ahci");
system("modprobe nvme");
/* Scan for root device */
printf("[initramfs] Scanning for root device...\n");
system("blkid");
system("lvm vgscan --mknodes 2>/dev/null");
/* Mount the real root filesystem */
printf("[initramfs] Mounting real root...\n");
if (mount("/dev/sda2", "/root", "ext4", MS_RDONLY, NULL) != 0) {
fprintf(stderr, "[initramfs] ERROR: Cannot mount root!\n");
fprintf(stderr, "[initramfs] Dropping to shell...\n");
system("/bin/sh");
return 1;
}
printf("[initramfs] Root filesystem mounted.\n");
/* Switch root to the real filesystem */
printf("[initramfs] Switching root to /root...\n");
/* Move mounts to the new root */
mount("/dev", "/root/dev", NULL, MS_MOVE, NULL);
mount("/proc", "/root/proc", NULL, MS_MOVE, NULL);
mount("/sys", "/root/sys", NULL, MS_MOVE, NULL);
/* chroot and execute the real init */
chdir("/root");
mount(".", "/", NULL, MS_MOVE, NULL);
chroot(".");
execl("/sbin/init", "init", NULL);
/* Should never reach here */
fprintf(stderr, "ERROR: exec init failed!\n");
return 1;
}
Expected output:
[initramfs] Starting early userspace...
[initramfs] Mounting /proc...
[initramfs] Mounting /sys...
[initramfs] Mounting /dev (devtmpfs)...
[initramfs] Loading storage drivers...
[initramfs] Scanning for root device...
[initramfs] Mounting real root...
[initramfs] Root filesystem mounted.
[initramfs] Switching root to /root...
Stage 5: systemd as PID 1
systemd takes over as the init Process (PID 1) and activates the default target.
# systemd boot analysis
systemd-analyze
systemd-analyze blame
systemd-analyze critical-chain
# List all systemd targets
systemctl list-units --type=target --all
# Check default target
systemctl get-default
# View boot log for specific service
journalctl -b -u ssh.service
# Check boot time in journal
journalctl --list-boots
# Boot into emergency mode (from GRUB, add 'systemd.unit=emergency.target')
import time
class SystemDUnit:
def __init__(self, name, description, wants=None, before=None,
after=None, service_type='simple'):
self.name = name
self.description = description
self.wants = wants or []
self.before = before or []
self.after = after or []
self.service_type = service_type
self.state = 'inactive'
self.activation_time = 0
self.pid = None
def activate(self, current_time):
print(f' Activating: {self.name} ({self.description})')
self.state = 'activating'
time.sleep(0.1)
self.state = 'active'
self.activation_time = current_time + 0.1
return self.activation_time
class SystemDManager:
def __init__(self):
self.units = {}
self.targets = {}
self.boot_time = 0
def add_unit(self, unit):
self.units[unit.name] = unit
def add_target(self, name, description, requires=None):
target = {
'name': name,
'description': description,
'requires': requires or [],
'state': 'inactive',
}
self.targets[name] = target
def boot_default_target(self):
default = 'graphical.target'
print(f'Starting {default}...')
self._activate_target(default)
print('Reached target Graphical Interface')
def _activate_target(self, target_name, depth=0):
target = self.targets.get(target_name)
if not target:
return
indent = ' ' * depth
print(f'{indent}[Target] {target["name"]}')
current_time = self.boot_time
for req in target['requires']:
if req in self.units:
t = self.units[req].activate(current_time)
current_time = max(current_time, t)
elif req in self.targets:
self._activate_target(req, depth + 1)
target['state'] = 'active'
systemd = SystemDManager()
systemd.boot_time = 0
# Add target hierarchy
systemd.add_target('local-fs.target', 'Local File Systems',
requires=['systemd-fsck-root.service'])
systemd.add_target('sysinit.target', 'System Initialization',
requires=['local-fs.target', 'udevd.service'])
systemd.add_target('basic.target', 'Basic System',
requires=['sysinit.target', 'dbus.service'])
systemd.add_target('multi-user.target', 'Multi-User System',
requires=['basic.target', 'ssh.service'])
systemd.add_target('graphical.target', 'Graphical Interface',
requires=['multi-user.target', 'display-manager.service'])
# Add units
for u in ['systemd-fsck-root.service', 'udevd.service', 'dbus.service',
'ssh.service', 'display-manager.service']:
systemd.add_unit(SystemDUnit(u, f'{u} description'))
print('systemd boot sequence:\n')
systemd.boot_default_target()
Expected output:
systemd boot sequence:
Starting graphical.target...
[Target] graphical.target
[Target] multi-user.target
Activating: systemd-fsck-root.service (systemd-fsck-root.service description)
[Target] basic.target
[Target] sysinit.target
Activating: udevd.service (udevd.service description)
Activating: dbus.service (dbus.service description)
[Target] local-fs.target
Activating: ssh.service (ssh.service description)
Activating: display-manager.service (display-manager.service description)
Reached target Graphical Interface
Common Mistakes
1. Breaking Boot by Editing GRUB Wrong
Editing /etc/default/grub and running update-grub without a backup can produce an unbootable system. Always keep a known-good kernel entry in GRUB (previous kernel is usually preserved).
2. Removing Old Kernels Without Testing
sudo apt autoremove removes old kernels. If the current kernel fails to boot, there's no fallback. Keep at least one previous kernel. Use sudo apt-mark hold linux-image-* on a known-good kernel.
3. Not Checking initramfs After Hardware Changes
After changing storage controllers or adding NVMe drives, the initramfs may not include the required drivers. Run sudo update-initramfs -u after hardware changes.
4. Ignoring systemd Critical Chain
Boot slowdowns are often caused by a single service with a long timeout. systemd-analyze critical-chain shows exactly which service is the bottleneck — don't guess.
5. Confusing dmesg Ring Buffer Size
The kernel ring buffer has limited size (usually 128KB). Old boot messages are overwritten. Use journalctl -k -b -1 to see the previous boot's messages.
Practice Questions
1. What is the difference between BIOS and UEFI boot? BIOS uses MBR Partitioning, runs in 16-bit real mode, and loads the first 512 bytes from disk. UEFI uses GPT Partitioning, runs in 64-bit mode, and loads an EFI application (.efi file) from the EFI System Partition (ESP). UEFI supports Secure Boot and has a boot manager.
2. What is the purpose of initramfs? initramfs provides the minimal drivers and tools needed to mount the real root filesystem. It contains storage drivers (SATA, NVMe, LVM, mdadm), filesystem modules (ext4, btrfs, xfs), and init scripts that discover and mount the root device.
3. How does systemd's parallel startup work?
systemd reads unit dependencies and activates units in parallel where the dependency graph allows. Network and SSH start simultaneously if neither depends on the other. systemd-analyze plot shows the parallel execution chart.
4. Challenge: Create a Shell Script that mimics the boot Process: display each stage with timestamps, simulate "loading" with progress bars for firmware, GRUB, kernel, initramfs, and systemd. Use stty for progress bar animation.
5. Real-World Task: Boot a Linux VM and run systemd-analyze blame. Identify the three slowest boot services. For each, read its unit file (systemctl cat <service>) and understand why it's slow (waiting for network? timeout? hardware probe?). Suggest one optimization.
Mini Project: Boot Logger
import time
import random
class BootProcessLogger:
"""Simulate timing the entire boot process"""
def __init__(self):
self.events = []
def log_event(self, phase, message, duration=0):
self.events.append({
'phase': phase,
'message': message,
'timestamp': sum(e.get('duration', 0) for e in self.events),
'duration': duration,
})
def simulate_boot(self):
total = random.uniform(8, 20)
elapsed = 0
self.log_event('firmware', 'UEFI initialization', random.uniform(1, 3))
self.log_event('firmware', 'Secure Boot check', random.uniform(0.1, 0.5))
self.log_event('firmware', 'Boot device selection', random.uniform(0.1, 0.3))
self.log_event('grub', 'GRUB2 loading', random.uniform(0.5, 1.5))
self.log_event('grub', 'Kernel loaded into memory', random.uniform(0.2, 0.8))
self.log_event('grub', 'initramfs loaded', random.uniform(0.1, 0.4))
self.log_event('kernel', 'Kernel decompression', random.uniform(0.3, 1.0))
self.log_event('kernel', 'CPU/PCI enumeration', random.uniform(1.0, 3.0))
self.log_event('kernel', 'Root device detection', random.uniform(0.2, 0.8))
self.log_event('initramfs', 'Mount /proc /sys /dev', random.uniform(0.1, 0.3))
self.log_event('initramfs', 'Load storage modules', random.uniform(0.3, 0.7))
self.log_event('initramfs', 'Switch root', random.uniform(0.1, 0.2))
self.log_event('systemd', 'systemd PID 1 start', random.uniform(0.1, 0.3))
self.log_event('systemd', 'Mount local filesystems', random.uniform(0.5, 2.0))
self.log_event('systemd', 'Start networking', random.uniform(0.5, 1.5))
self.log_event('systemd', 'Start display manager', random.uniform(1.0, 3.0))
def print_timeline(self):
print(f'{"Phase":<12} {"Event":<35} {"Time (s)":<10} {"Duration (s)":<12}')
print('-' * 69)
for ev in self.events:
print(f'{ev["phase"]:<12} {ev["message"]:<35} '
f'{ev["timestamp"]:<10.2f} {ev["duration"]:<12.2f}')
total = sum(e['duration'] for e in self.events)
print(f'\nTotal boot time: {total:.2f}s')
boot = BootProcessLogger()
boot.simulate_boot()
boot.print_timeline()
Expected output:
Phase Event Time (s) Duration (s)
---------------------------------------------------------------------
firmware UEFI initialization 0.00 2.34
firmware Secure Boot check 2.34 0.23
firmware Boot device selection 2.57 0.15
grub GRUB2 loading 2.72 1.12
...
systemd Start display manager 12.45 2.10
Total boot time: 14.55s
FAQ
Related Concepts
What's Next
You now understand the Linux boot process. Next, learn about embedded Linux systems to see how the boot Process changes on resource-constrained devices, or explore system calls for the kernel-userspace interface.
- Practice daily — Each time you boot a system, time it with
systemd-analyzeand note the total. - Build a project — Create a minimal kernel + initramfs that boots to a shell. Use BusyBox for the initramfs.
- Explore related topics — Study UEFI application development and bootkit security.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro