Loading learning content...
When firmware completes its initialization and locates a bootable device, it faces a limitation: firmware understands how to read basic file systems and execute binary code, but it knows nothing about operating system kernels—their specific loading requirements, command-line parameters, initial RAM disks, or the complex handshake required to properly transfer control.
This is where the bootloader enters the picture. The bootloader is a specialized program that bridges the gap between firmware's generic capabilities and the operating system's specific needs. It understands kernel formats, manages boot options, configures hardware state, and orchestrates the delicate transition from firmware environment to kernel environment.
GRUB (GRand Unified Bootloader) is the dominant bootloader in the Linux ecosystem, but understanding bootloaders requires grasping the fundamental challenges they solve—challenges that apply regardless of the specific bootloader implementation.
By the end of this page, you will understand: why bootloaders are necessary; the stages of bootloader execution; GRUB's architecture and design philosophy; how to configure GRUB for complex multi-boot scenarios; the kernel loading process including initramfs; and how bootloaders handle the BIOS-to-UEFI transition.
Couldn't firmware simply load the kernel directly? After all, UEFI can read FAT32 file systems and execute EFI applications. Why add another layer of complexity?
The answer lies in the diverse requirements that a bootloader addresses:
The Abstraction Problem:
Operating system kernels have specific requirements for how they're loaded:
Firmware doesn't understand these requirements—it just executes EFI applications. The bootloader translates between firmware's capabilities and kernel's expectations.
Modern Linux kernels can be compiled with EFI Stub support, allowing UEFI to boot them directly as EFI applications. This eliminates the need for GRUB in simple configurations. However, GRUB remains valuable for multi-boot systems, complex configurations, and the flexibility of runtime boot option modification.
The File System Challenge:
Consider this scenario: Your Linux root partition uses Btrfs with zlib compression. UEFI only understands FAT32. How does the kernel get loaded from a file system firmware can't read?
The bootloader solves this by:
This is why GRUB is large and complex—it's essentially a miniature operating system with its own module system, file system drivers, and execution environment.
Historically, bootloaders used a multi-stage design to overcome storage constraints. While UEFI relaxes some of these constraints, understanding the staging concept remains important—GRUB still uses stages, and the architecture reflects deep design decisions.
Legacy BIOS Staging:
With only 446 bytes available in the MBR, the boot process had to chain multiple stages:
| Stage | Location | Size | Purpose |
|---|---|---|---|
| Stage 1 | MBR (first 446 bytes) | ~440 bytes | Load Stage 1.5 from known disk location |
| Stage 1.5 | Post-MBR gap (sectors 1-63) or BIOS boot partition | ~30 KB | Provides file system drivers to read /boot |
| Stage 2 | /boot/grub directory on boot partition | Variable (MB) | Full GRUB core, modules, menu, configuration |
Stage 1: The Bootstrap
Stage 1 has an impossibly constrained task: in under 512 bytes, it must:
Stage 1 uses BIOS INT 13h (disk services) to read Stage 1.5. The location of Stage 1.5 is embedded directly in Stage 1's code during installation—there's no room for a filesystem driver.
Stage 1.5: The Bridge
Stage 1.5 is stored in the gap between the MBR and the first partition (traditionally sectors 1-62), or in a dedicated BIOS boot partition on GPT disks. It contains:
Stage 2: The Full Bootloader
Stage 2, located in /boot/grub, is the complete GRUB environment:
With UEFI, the staging complexity largely disappears. GRUB installs as a single EFI application (grubx64.efi) on the ESP. This file contains the core bootloader, and it loads additional modules from the ESP or from /boot/grub on the system partition. No 446-byte constraint, no post-MBR gaps—just a standard executable.
GRUB 2 (the current version, despite often being called just "GRUB") is a complete rewrite of GRUB Legacy. It features a modular architecture where functionality is split into a core and dynamically loaded modules.
Core Components:
Platform Support:
GRUB builds for multiple platforms, each requiring different code:
| Platform | Boot Method | Typical Use |
|---|---|---|
| i386-pc | BIOS boot from MBR | Legacy BIOS systems, most common historically |
| x86_64-efi | UEFI boot as EFI app | Modern 64-bit UEFI systems |
| i386-efi | UEFI 32-bit boot | Some tablets, older Macs, Bay Trail systems |
| arm64-efi | UEFI on ARM64 | ARM servers, Raspberry Pi 4 with UEFI |
| powerpc-ieee1275 | OpenFirmware boot | PowerPC Macs, IBM POWER systems |
| sparc64-ieee1275 | OpenFirmware boot | Sun/Oracle SPARC systems |
12345678910111213141516171819202122232425262728293031323334353637
GRUB Installation Directory Structure====================================== /boot/grub/ (or /boot/grub2 on some distros)├── grub.cfg # Main configuration file (generated)├── grubenv # Persistent environment block (1024 bytes)├── fonts/│ └── unicode.pf2 # Font for graphical menu├── locale/ # Translations for menu├── themes/ # Graphical theme files│ └── starfield/│ ├── theme.txt│ └── *.png├── i386-pc/ # BIOS platform modules (one of these)│ ├── normal.mod # Normal boot mode│ ├── ext2.mod # ext2/3/4 filesystem│ ├── xfs.mod # XFS filesystem │ ├── btrfs.mod # Btrfs filesystem│ ├── part_msdos.mod # MBR partition map│ ├── part_gpt.mod # GPT partition map│ ├── linux.mod # Linux kernel loader│ ├── chain.mod # Chain loading (for Windows)│ ├── gfxterm.mod # Graphical terminal│ └── ... # Many more modules└── x86_64-efi/ # UEFI platform modules ├── normal.mod ├── ext2.mod └── ... /boot/efi/EFI/ # ESP (UEFI systems only)├── BOOT/│ └── BOOTX64.EFI # Fallback bootloader└── ubuntu/ # (or fedora, etc.) ├── shimx64.efi # Signed shim (if Secure Boot) ├── grubx64.efi # GRUB EFI application ├── grub.cfg # Minimal config pointing to /boot/grub └── BOOTX64.CSV # Boot entry registration fileGRUB loads modules on demand. The core image embedded at installation includes only essential modules (for reading the /boot partition). Commands like 'linux' and 'initrd' trigger loading of additional modules. This keeps the initial bootloader small while providing extensive functionality.
GRUB uses a scripting language for configuration. The main configuration file, grub.cfg, is typically generated by tools like grub-mkconfig from templates in /etc/grub.d/ and settings in /etc/default/grub. Understanding both the high-level configuration and the underlying scripting language is essential for advanced boot management.
Configuration Generation:
12345678910111213141516171819202122232425262728293031323334
# /etc/default/grub - High-level GRUB configuration # Default menu entry (by index or ID)GRUB_DEFAULT=0# GRUB_DEFAULT="Ubuntu, with Linux 6.2.0-generic" # By exact title# GRUB_DEFAULT=saved # Use grubenv saved_entry # Timeout before auto-boot (seconds)GRUB_TIMEOUT=5GRUB_TIMEOUT_STYLE=menu # menu, countdown, or hidden # Kernel command line for normal entriesGRUB_CMDLINE_LINUX_DEFAULT="quiet splash" # Kernel command line for ALL entries (including recovery)GRUB_CMDLINE_LINUX="crashkernel=auto" # Disable recovery menu entriesGRUB_DISABLE_RECOVERY="false" # Console settingsGRUB_TERMINAL="console" # console, serial, gfxterm# GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0" # Graphics mode for gfxtermGRUB_GFXMODE="1920x1080"GRUB_GFXPAYLOAD_LINUX="keep" # Keep resolution into kernel # OS detectionGRUB_DISABLE_OS_PROBER=false # Find other OSes for dual-boot # After editing, regenerate grub.cfg:# $ sudo update-grub # Debian/Ubuntu# $ sudo grub2-mkconfig -o /boot/grub2/grub.cfg # Fedora/RHELThe grub.cfg Structure:
While you shouldn't edit grub.cfg directly (it's regenerated), understanding its structure helps with debugging:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
# /boot/grub/grub.cfg - Generated configuration example# DO NOT EDIT - Generated by grub-mkconfig # Environment blockload_env # Default entry and timeoutset default="0"set timeout=5 # Graphics setupinsmod gfxtermset gfxmode=autoterminal_output gfxterm # Menu entry for Ubuntumenuentry 'Ubuntu, with Linux 6.2.0-39-generic' --class ubuntu --class gnu-linux { # Record boot selection recordfail savedefault # Load required modules insmod gzio insmod part_gpt insmod ext2 # Set root to boot partition # (hd0,gpt2) = first disk, second GPT partition set root='hd0,gpt2' search --no-floppy --fs-uuid --set=root a1b2c3d4-e5f6-7890-abcd-ef1234567890 # Load kernel with parameters linux /vmlinuz-6.2.0-39-generic root=UUID=... ro quiet splash # Load initial ramdisk initrd /initrd.img-6.2.0-39-generic} # Recovery mode entrymenuentry 'Ubuntu, with Linux 6.2.0-39-generic (recovery mode)' --class ubuntu { insmod gzio insmod part_gpt insmod ext2 set root='hd0,gpt2' linux /vmlinuz-6.2.0-39-generic root=UUID=... ro recovery nomodeset initrd /initrd.img-6.2.0-39-generic} # Chain load Windowsmenuentry 'Windows Boot Manager' --class windows { insmod part_gpt insmod fat insmod chain set root='hd0,gpt1' chainloader /EFI/Microsoft/Boot/bootmgfw.efi} # Submenus group older kernelssubmenu 'Advanced options for Ubuntu' { menuentry 'Ubuntu, with Linux 6.1.0-35-generic' { ... } menuentry 'Ubuntu, with Linux 6.0.0-28-generic' { ... }}insmod <module> — Load a GRUB module (filesystem driver, partition map, etc.)set root=<device> — Set the device to read files from (kernel, initrd)search --fs-uuid --set=root <uuid> — Find partition by UUID (portable across disk reordering)linux <kernel> <params> — Load a Linux kernel with command-line parametersinitrd <image> — Load an initial ramdisk to pass to the kernelchainloader <file> — Load and execute another bootloader (for Windows, etc.)boot — Execute the loaded kernel (implicit at end of menuentry)Never edit /boot/grub/grub.cfg directly—it will be overwritten. Instead, place custom entries in /etc/grub.d/40_custom or create new scripts. Files in /etc/grub.d/ are executed in alphabetical order during grub-mkconfig, and their output forms grub.cfg.
GRUB provides an interactive command line accessible by pressing 'c' at the boot menu. This is invaluable for boot recovery, debugging, and understanding how GRUB works. Additionally, pressing 'e' at a menu entry allows editing that entry before booting.
Why Use the GRUB Command Line?
123456789101112131415161718192021222324252627282930313233343536373839404142
# GRUB Command Line Interactive Session grub> help# Displays list of available commands grub> ls(hd0) (hd0,gpt1) (hd0,gpt2) (hd0,gpt3) (proc) grub> ls (hd0,gpt2)/vmlinuz vmlinuz.old initrd.img initrd.img.old lost+found/ boot/ etc/ ... grub> ls (hd0,gpt2)/boot/grub/ vmlinuz-6.2.0-39-generic initrd.img-6.2.0-39-generic ... grub> cat (hd0,gpt2)/etc/fstab# Shows file contents - useful for finding root partition UUID grub> set# Shows all environment variablesroot=hd0,gpt2prefix=(hd0,gpt2)/boot/grub... # Manual boot sequencegrub> set root=(hd0,gpt2)grub> linux /vmlinuz root=/dev/sda3 ro singlegrub> initrd /initrd.imggrub> boot # Boot Windows (chain loading)grub> set root=(hd0,gpt1)grub> chainloader /EFI/Microsoft/Boot/bootmgfw.efigrub> boot # Finding root by UUID (more reliable)grub> search --fs-uuid --set=root a1b2c3d4-5678-90ab-cdef-1234567890abgrub> ls ($root)/boot/ # Load specific kernel versiongrub> linux /boot/vmlinuz-6.1.0-35-generic root=UUID=... ro nomodesetgrub> initrd /boot/initrd.img-6.1.0-35-genericgrub> bootls — List devices, partitions, or directory contentscat <file> — Display file contents (useful for checking configs)set <var>=<value> — Set environment variableunset <var> — Remove environment variableexport <var> — Make variable available to loaded kernelsearch --fs-uuid --set=root <uuid> — Find and set root partitionconfigfile <path> — Load and execute a grub.cfgnormal — Return to normal menu modereboot — Reboot the systemhalt — Power off the systemGRUB's command line supports tab completion. Type partial commands, file paths, or partition names and press Tab to complete. Use Tab-Tab to see all possibilities. This makes navigating an unfamiliar system much easier during recovery scenarios.
Common Recovery Scenarios:
Scenario 1: Broken grub.cfg
grub> set root=(hd0,gpt2)
grub> linux /vmlinuz root=/dev/sda2 ro
grub> initrd /initrd.img
grub> boot
# Once booted, regenerate: sudo update-grub
Scenario 2: Wrong root partition
# Press 'e' at menu entry, find 'linux' line, change root=...
# Press Ctrl+X or F10 to boot with changes
Scenario 3: Graphics driver crash
# Edit menu entry (press 'e'), add to linux line:
linux ... nomodeset
# This disables kernel mode-setting, using basic VGA
When GRUB executes the linux and initrd commands followed by boot, it initiates a precisely choreographed process to load and execute the kernel. This process must satisfy strict requirements about memory layout, CPU state, and information passing.
The Linux Boot Protocol:
Linux kernels follow a documented boot protocol that defines:
| Information | How Passed | Purpose |
|---|---|---|
| Command line | boot_params.hdr.cmd_line_ptr | Kernel parameters (root=, init=, etc.) |
| Memory map | boot_params.e820_table[] | Usable/reserved memory regions |
| Initrd location | boot_params.hdr.ramdisk_image/size | Address and size of initial ramdisk |
| Video mode | boot_params.screen_info | Current video configuration |
| ACPI tables | Pointed by RSDP in memory | Hardware description (firmware provides) |
| Boot loader ID | boot_params.hdr.type_of_loader | Identifies which bootloader loaded kernel |
123456789101112131415161718192021222324252627282930313233343536373839
// Simplified view of Linux boot_params structure// Full definition: arch/x86/include/uapi/asm/bootparam.h struct boot_params { struct screen_info screen_info; // Video mode information struct apm_bios_info apm_bios_info; // APM BIOS data __u8 _pad2[4]; __u64 tboot_addr; // TXT (Trusted Execution) address struct ist_info ist_info; // Intel SpeedStep info __u64 acpi_rsdp_addr; // ACPI Root System Description Pointer __u8 _pad3[8]; __u8 hd0_info[16]; // BIOS disk parameters __u8 hd1_info[16]; struct sys_desc_table sys_desc_table; struct olpc_ofw_header olpc_ofw_header; __u32 ext_ramdisk_image; // Initrd > 4GB support __u32 ext_ramdisk_size; __u32 ext_cmd_line_ptr; // Command line > 4GB __u8 _pad4[116]; struct edid_info edid_info; // Monitor EDID data struct efi_info efi_info; // EFI firmware information __u32 alt_mem_k; __u32 scratch; __u8 e820_entries; // Number of memory map entries __u8 eddbuf_entries; __u8 edd_mbr_sig_buf_entries; __u8 kbd_status; __u8 secure_boot; // Secure Boot state __u8 _pad5[2]; __u8 sentinel; __u8 _pad6[1]; struct setup_header hdr; // THE CRITICAL HEADER __u8 _pad7[0x290-0x1f1-sizeof(struct setup_header)]; __u32 edd_mbr_sig_buffer[EDD_MBR_SIG_MAX]; struct boot_e820_entry e820_table[E820_MAX_ENTRIES]; // Memory map __u8 _pad8[48]; struct edd_info eddbuf[EDDMAXNR]; __u8 _pad9[276];} __attribute__((packed));When booted directly by UEFI as an EFI application (via EFI Stub), the kernel receives information through EFI Boot Services rather than boot_params. The kernel's EFI stub code queries UEFI for memory maps, command line (from UEFI load options), and handles the ExitBootServices transition before jumping to the main kernel entry point.
The kernel faces a fundamental chicken-and-egg problem: it needs to mount the root filesystem, but the drivers required to access that filesystem might not be compiled into the kernel. They could be:
The solution is initramfs (initial RAM filesystem)—a compressed archive containing a minimal temporary root filesystem that the kernel uses during early boot.
123456789101112131415161718192021222324252627282930313233343536373839
# Initramfs Operations on Linux # List contents of initramfs (cpio archive, often gzip compressed)$ lsinitramfs /boot/initrd.img-$(uname -r).binbin/busyboxbin/catbin/sh...scripts/scripts/init-top/scripts/init-premount/scripts/local-top/scripts/local-premount/...lib/modules/6.2.0-39-generic/lib/modules/6.2.0-39-generic/kernel/drivers/ata/lib/modules/6.2.0-39-generic/kernel/drivers/md/... # Extract initramfs for examination$ mkdir /tmp/initrd-contents$ cd /tmp/initrd-contents$ unmkinitramfs /boot/initrd.img-$(uname -r) .# or: zcat /boot/initrd.img-... | cpio -idmv # Regenerate initramfs (Debian/Ubuntu)$ sudo update-initramfs -u # Update current kernel$ sudo update-initramfs -u -k all # Update all kernels # Regenerate initramfs (Fedora/RHEL)$ sudo dracut --force # Add specific modules to initramfs# /etc/initramfs-tools/modules (Debian) or /etc/dracut.conf.d/*.conf (Fedora) # Debug: Boot with rd.shell (dracut) or break=top (initramfs-tools)# Drops to shell at specified point in init scriptIf initramfs lacks required modules (e.g., your new RAID controller's driver), the system cannot mount root and will drop to an initramfs shell. The solution: boot from rescue media, chroot into the system, add the driver to initramfs configuration, and regenerate. This is why kernel updates regenerate initramfs automatically.
GRUB excels at managing systems with multiple operating systems or kernel versions. Understanding multi-boot is essential for development environments, testing, and systems running both Linux and Windows.
Detection with os-prober:
GRUB uses a tool called os-prober to detect other operating systems. When grub-mkconfig runs, os-prober scans all partitions for bootable systems and adds them to grub.cfg.
123456789101112131415161718192021222324252627282930313233343536373839404142434445
# Multi-Boot Configuration Examples # 1. Detect other operating systems$ sudo os-prober/dev/sda1@/EFI/Microsoft/Boot/bootmgfw.efi:Windows Boot Manager:Windows:efi/dev/sda5:Fedora:Fedora:linux # 2. Ensure os-prober is enabled in /etc/default/grubGRUB_DISABLE_OS_PROBER=false # 3. Regenerate grub.cfg to include detected systems$ sudo update-grubGenerating grub configuration file ...Found linux image: /boot/vmlinuz-6.2.0-39-genericFound initrd image: /boot/initrd.img-6.2.0-39-genericFound Windows Boot Manager on /dev/sda1@/EFI/Microsoft/Boot/bootmgfw.efiFound Fedora on /dev/sda5done # Chain loading Windows manually (from /etc/grub.d/40_custom)menuentry "Windows 11" { insmod part_gpt insmod fat insmod chain search --no-floppy --fs-uuid --set=root AAAA-BBBB # ESP UUID chainloader /EFI/Microsoft/Boot/bootmgfw.efi} # Booting another Linux with separate /bootmenuentry "Fedora Workstation" { insmod part_gpt insmod ext2 search --no-floppy --fs-uuid --set=root <fedora-root-uuid> linux /boot/vmlinuz root=UUID=<fedora-root-uuid> ro initrd /boot/initramfs.img} # Booting from a separate /boot partitionmenuentry "Arch Linux" { insmod part_gpt insmod ext2 search --no-floppy --fs-uuid --set=root <arch-boot-uuid> linux /vmlinuz-linux root=UUID=<arch-root-uuid> ro initrd /initramfs-linux.img}Boot Order and Default Selection:
| Method | Configuration | Use Case |
|---|---|---|
| Index | GRUB_DEFAULT=0 | First entry; simple but breaks if entries change |
| Exact title | GRUB_DEFAULT="Ubuntu, with Linux 6.2.0" | Explicit; breaks on kernel upgrades |
| Saved | GRUB_DEFAULT=saved + GRUB_SAVEDEFAULT=true | Remembers last choice; persists across boots |
| One-time | grub-reboot "Windows" | Boot once to specified entry, then revert |
| Subentry | GRUB_DEFAULT="1>2" | Third entry in second submenu |
For one-time boots to a specific OS (like rebooting to Windows for updates), use: sudo grub-reboot "Windows Boot Manager" then sudo reboot. The system boots Windows once, then reverts to the default entry. This is much easier than changing BIOS/UEFI boot order or waiting at the GRUB menu.
GRUB installation differs significantly between BIOS and UEFI systems. Understanding both methods is crucial for system administration and boot troubleshooting.
BIOS Installation:
1234567891011121314151617181920212223242526272829
# GRUB Installation - BIOS Systems # Basic installation to MBR of /dev/sda$ sudo grub-install /dev/sda # Device, not partition!Installing for i386-pc platform.Installation finished. No error reported. # What grub-install does:# 1. Writes Stage 1 to MBR (first 446 bytes of /dev/sda)# 2. Writes Stage 1.5 to post-MBR gap (or BIOS boot partition)# 3. Installs GRUB modules to /boot/grub/i386-pc/# 4. Generates core.img embedded with filesystem driver # With explicit options$ sudo grub-install --target=i386-pc --boot-directory=/boot /dev/sda # For GPT disks with BIOS: Need BIOS Boot Partition (type ef02)# Create with: sgdisk -n 0:0:+1M -t 0:ef02 /dev/sda# grub-install will use this partition for core.img # Reinstall from live environment$ sudo mount /dev/sda2 /mnt # Mount root$ sudo mount --bind /dev /mnt/dev$ sudo mount --bind /proc /mnt/proc$ sudo mount --bind /sys /mnt/sys$ sudo chroot /mnt$ grub-install /dev/sda$ update-grub$ exit123456789101112131415161718192021222324252627282930313233343536
# GRUB Installation - UEFI Systems # Basic installation (ESP must be mounted at /boot/efi)$ sudo grub-install --target=x86_64-efi --efi-directory=/boot/efiInstalling for x86_64-efi platform.Installation finished. No error reported. # What grub-install does (UEFI):# 1. Copies grubx64.efi to ESP (/boot/efi/EFI/ubuntu/)# 2. Installs modules to /boot/grub/x86_64-efi/# 3. Registers boot entry in UEFI NVRAM (via efibootmgr) # With Secure Boot shim$ sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi \ --bootloader-id=ubuntu --uefi-secure-boot # View resulting UEFI boot entry$ efibootmgr -vBoot0001* ubuntu HD(1,GPT,...)/File(\EFI\ubuntu\shimx64.efi) # Reinstall from live environment (UEFI)$ sudo mount /dev/nvme0n1p2 /mnt # Mount root$ sudo mount /dev/nvme0n1p1 /mnt/boot/efi # Mount ESP$ sudo mount --bind /dev /mnt/dev$ sudo mount --bind /proc /mnt/proc$ sudo mount --bind /sys /mnt/sys$ sudo mount --bind /sys/firmware/efi/efivars /mnt/sys/firmware/efi/efivars$ sudo chroot /mnt$ grub-install --target=x86_64-efi --efi-directory=/boot/efi$ update-grub$ exit # Fallback reinstall without NVRAM modification$ sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi \ --removable# This installs to /EFI/BOOT/BOOTX64.EFI - always worksOn BIOS: Don't run grub-install /dev/sda1 (partition)—use /dev/sda (device). On UEFI: Don't forget to mount the ESP before running grub-install. After Windows updates break GRUB: Don't reinstall Windows, just reinstall GRUB from a live USB. The Windows Boot Manager entry remains; just the UEFI boot order changes.
The bootloader transforms firmware's generic boot capability into the specific requirements of operating system kernels. GRUB dominates the Linux ecosystem, but the concepts apply across platforms and bootloaders.
What's Next:
GRUB has loaded the kernel and initramfs into memory and transferred control. The next page examines what happens inside the kernel—from the first instruction at the kernel entry point through hardware initialization to the creation of the first user-space process.
You now understand the bootloader layer: why it's necessary, how GRUB is structured, configuration and customization, command-line recovery, initramfs's role, and multi-boot scenarios. GRUB's handoff to the kernel is the pivotal moment where control leaves pre-boot environment and enters the operating system proper.