GRUB has read its configuration, shown a menu, and the user (or the timeout) has selected an entry. The linux command in that entry specifies a path: something like /vmlinuz-6.2.0 or /boot/vmlinuz-6.2.0. GRUB must now find that file on disk, figure out what kind of image it is, and load it into the right place in memory.
This sounds simple, and for GRUB it mostly is -- GRUB has filesystem drivers that can read ext4, XFS, and other formats. But the kernel image itself is not straightforward. It is a compressed, multi-part file with a specific internal structure that GRUB must understand. The name vmlinuz hints at that structure, and the story behind it reaches back decades.
Where the Kernel Lives
On most Linux systems, kernel images live in /boot. This directory is sometimes on the root filesystem and sometimes on a separate small partition. A separate /boot partition was historically necessary because older bootloaders could not read beyond certain disk boundaries, or could not understand the root filesystem's format. Today, GRUB can handle most filesystems and disk sizes, but the convention persists.
Inside /boot, you will typically find several files for each installed kernel:
/boot/vmlinuz-6.2.0-36-generic
/boot/initrd.img-6.2.0-36-generic
/boot/config-6.2.0-36-generic
/boot/System.map-6.2.0-36-generic
The version number in each filename matches a specific kernel build. When you upgrade the kernel, your package manager installs new files with the new version number and runs grub-mkconfig to regenerate the GRUB configuration with updated paths.
The symbolic link /vmlinuz (in the root directory) often points to the latest installed kernel image in /boot. GRUB configurations may reference either the full path or the symlink.
The Kernel Image Format
The file vmlinuz is not a simple executable. It is not an ELF binary that you could run with a normal program loader. It is a custom format specific to the Linux boot process, and it contains several distinct parts packed together.
Think of it like a shipping container. The outside of the container has labels and handling instructions (the setup header). Inside the container is a smaller box containing the actual cargo, wrapped in packing material (the compressed kernel, surrounded by the decompression stub). You cannot use the cargo until you unpack the box.
The three parts serve different purposes:
The boot sector and setup header occupy the first portion of the file. The boot sector is a legacy artifact -- it allows the kernel to be booted directly from a floppy disk without a bootloader. Nobody does this anymore, but the structure remains. The setup header, however, is critical. It contains fields that GRUB reads to know the kernel's version, where to load it in memory, what boot protocol version it supports, and the size of the setup code.
The decompression stub is a small self-contained program. Its job is to decompress the next part. We will cover this in detail in the next article.
The compressed kernel is the actual Linux kernel -- the vmlinux ELF binary, stripped and then compressed with gzip, LZ4, LZMA, XZ, or Zstandard (depending on the kernel's build configuration).
vmlinux vs. vmlinuz vs. bzImage
These names are confusing because they evolved over decades and carry historical baggage. Here is the lineage:
vmlinux is the raw, uncompressed Linux kernel. It is an ELF binary -- the direct output of the kernel build's linking step. It contains debug symbols and section headers. On a modern kernel, it can be 30-80 MB or more. You will find it in the kernel build tree but almost never in /boot.
zImage was the original compressed kernel format. The "z" stands for the compression. It was limited to 512 KB of compressed data because it loaded the kernel below the 1 MB mark in memory (the real-mode address space). This limit made it unusable for modern kernels.
bzImage stands for "big zImage." It lifts the size restriction by loading the compressed kernel above the 1 MB mark. Despite the name containing "bz," the compression is not bzip2 -- it was historically gzip, and modern kernels can use several algorithms. Nearly every Linux system today uses bzImage.
vmlinuz is the file you find in /boot. It is a bzImage -- the same file, just installed with a different name.
The Setup Header
The setup header is the critical interface between the bootloader and the kernel. It is a binary structure starting at offset 0x01F1 in the kernel image, and it tells the bootloader everything it needs to know about how to load this particular kernel.
GRUB reads the following fields from the setup header:
- setup_sects: How many 512-byte sectors the setup code occupies. This tells GRUB where the protected-mode kernel code begins in the file.
- syssize: The size of the protected-mode code, in 16-byte paragraphs.
- boot_flag: Must be 0xAA55 (same magic number as the MBR, for historical reasons).
- header: Must be the magic string "HdrS" (0x53726448). This confirms the file is a valid Linux kernel image.
- version: The boot protocol version. Different versions support different features. Modern kernels use protocol 2.15 or higher.
- loadflags: Bit flags that control loading behavior. Bit 0 indicates whether the kernel was compiled as a bzImage (loaded high) or a zImage (loaded low).
- cmd_line_ptr: The physical address where GRUB should place the kernel command-line string.
- initrd_addr_max: The maximum physical address where the initrd can end. This prevents GRUB from loading the initrd above the kernel's addressable range.
- ramdisk_image and ramdisk_size: Where GRUB loaded the initrd and how large it is. GRUB fills these in so the kernel can find the initrd later.
How GRUB Loads the Image
When GRUB processes the linux command, it executes a precise loading sequence:
-
Open the file. GRUB uses its filesystem driver to open
vmlinuzfrom the specified partition and path. -
Read the setup header. GRUB reads the first few kilobytes of the file and parses the setup header starting at offset 0x01F1. It checks for the "HdrS" magic and the boot protocol version.
-
Validate the image. GRUB checks that the
loadflagsindicate a bzImage (it refuses to load a zImage on modern systems where the kernel would not fit below 1 MB). It verifies the protocol version is recent enough to support the features GRUB needs. -
Load the real-mode code. The first
setup_sects + 1sectors of the file (the boot sector plus the setup sectors) contain the real-mode kernel code. GRUB loads this to a low memory address, typically around 0x10000 (64 KB). -
Load the protected-mode code. Everything after the setup sectors is the protected-mode kernel -- the decompression stub followed by the compressed kernel. GRUB loads this at the 1 MB mark (address 0x100000) or higher if the setup header specifies a preferred address.
-
Store boot parameters. GRUB writes the kernel command line to memory and records its address in
cmd_line_ptr. It fills in the memory map, video mode information, and other fields in the boot parameters structure (the "zero page" at the start of the real-mode code segment).
The initrd Companion
When GRUB processes the initrd command, it loads the initramfs image into memory above the kernel. The setup header field initrd_addr_max tells GRUB the highest address the kernel can access for the initrd, and GRUB loads it as high as possible within that limit (to leave room for the kernel to decompress and expand).
GRUB then writes the initrd's physical address and size into the ramdisk_image and ramdisk_size fields of the boot parameters. The kernel will read these values during its initialization to find the initramfs.
The initrd is not optional on most systems. Without it, the kernel would need every driver compiled in -- SATA, NVMe, SCSI, USB, LVM, dm-crypt, every filesystem -- to mount the root partition. The initramfs provides exactly the drivers needed for this specific machine, keeping the kernel itself smaller and more generic.
Finding the Kernel on UEFI
On UEFI systems, the process is slightly different but the kernel image format is the same. The UEFI version of GRUB reads files from the EFI System Partition (or from the /boot partition, which GRUB can access via its filesystem modules). The kernel is still a bzImage with the same setup header.
One notable UEFI feature: modern Linux kernels (since about 2011, with the EFI boot stub) can be booted directly by the firmware without GRUB at all. The kernel's EFI stub makes the bzImage look like a valid EFI application. The firmware loads it, the EFI stub runs, sets up the boot parameters itself, and jumps to the kernel. This is the basis of systemd-boot and other simple UEFI boot managers.
But whether GRUB or the firmware loads the image, the kernel's internal structure is the same. The setup header, the decompression stub, and the compressed kernel are all present. The next step is always the same: decompress.
What Happens Next
GRUB has found the kernel file, parsed its setup header, loaded its real-mode and protected-mode parts into the correct memory addresses, and filled in the boot parameters. The initramfs is in memory too. Everything is staged and ready.
Now the CPU must jump to the kernel's entry point. But the code it jumps to is not the kernel itself -- it is the decompression stub. The real kernel is still compressed, packed inside the bzImage. The stub must unpack it, and to do that, it first needs to leave real mode behind.