Skip to content

The Boot Process

The sequence from applying power to a machine to a kernel’s first instruction executing spans several distinct owners of the CPU, each handing control to the next through a well-defined, if somewhat historically accumulated, convention. Understanding this sequence matters for kernel development specifically because a kernel’s earliest code inherits whatever state the previous stage left behind: an environment with only as many guarantees as firmware and a bootloader chose to provide, and considerably fewer than the CPU is ultimately capable of.

The very first code to execute on any x86 machine belongs to firmware, not to an operating system: either a legacy BIOS or, on current hardware, UEFI. Firmware performs the Power-On Self Test (POST), initializes essential hardware including the memory controller, and then locates something to hand control to. This is also where the physical memory map a kernel eventually relies on originates, since firmware is what actually queried the memory controller to produce it.

A BIOS locates a bootable device by reading its first 512-byte sector, the Master Boot Record, and checking for the signature bytes 0x55, 0xAA at its very end; if present, BIOS loads that entire sector to physical address 0x7C00 and jumps to it, still in 16-bit real mode, with essentially nothing else set up. UEFI instead locates and directly executes a .efi PE-format executable from a FAT-formatted EFI System Partition, running with far more of the platform already initialized, including, notably, already in a flat, non-segmented addressing mode rather than real mode’s segment:offset scheme, which is why UEFI-targeted boot code looks considerably more like ordinary C than the assembly a BIOS-era boot sector generally requires.

Because a legacy MBR sector has only 512 bytes to work with (minus the two bytes consumed by the boot signature), it rarely contains a kernel’s actual loading logic directly. Instead, it typically loads a larger second-stage bootloader from disk, which then performs the more involved work: reading a kernel image from the filesystem, parsing whatever format that kernel uses to describe its own loading requirements (commonly Multiboot), transitioning the CPU out of real mode, setting up a minimal environment the kernel can rely on, and finally jumping into the kernel’s own entry point. Many hobby kernels avoid writing this stage themselves entirely by relying on an existing bootloader such as GRUB, which already implements Multiboot loading, filesystem parsing, and the real-to-protected-mode transition, at the cost of depending on an external tool rather than controlling every step personally, a tradeoff discussed further under Bootloaders.

Whatever loads the kernel, a custom bootloader or an existing one like GRUB, is responsible for getting the CPU out of the 16-bit real mode it starts in, since essentially no kernel intends to run there permanently. The transition to 32-bit protected mode requires a Global Descriptor Table already loaded and CR0’s protection-enable bit set, followed immediately by a far jump to flush the CPU’s internal instruction pipeline of any real-mode-decoded instructions still in flight. The A20 line also needs enabling around this same point, before anything tries to rely on memory above 1 MB behaving correctly. A kernel targeting 64-bit long mode has further work still ahead of it even after this: long mode additionally requires paging already active with PAE enabled, which is why many kernels perform this second transition themselves, early in their own entry point, rather than expecting a bootloader to have completed it on their behalf; GRUB’s own Multiboot loading, notably, hands off control in 32-bit protected mode, not 64-bit long mode, leaving that final step to the kernel.

By the time a kernel’s own code begins executing, exactly how much of the CPU’s state is trustworthy depends entirely on what loaded it and by what convention. A Multiboot-compliant bootloader guarantees a specific, documented set of facts on entry: a particular magic value in one register confirming Multiboot was actually used, a pointer to the boot information structure (containing the memory map and other details) in another, paging disabled, interrupts disabled. A kernel’s entry code can rely on exactly that set of guarantees and no more. A kernel entered by less standardized means has correspondingly fewer guarantees and must establish more of its own environment (a valid stack, a GDT it trusts, an IDT) before it can safely do much of anything beyond the most minimal setup.

A boot sector’s 512-byte size limit is stricter in practice than it first appears, since it has to include not just code but the trailing boot signature, and any data the code itself needs (strings, a partial GDT, disk-read parameters) competes with instructions for the same tiny budget; a common early exercise is simply getting a “hello world” message printed from within this constraint before attempting anything more elaborate. The transition out of real mode is also unusually unforgiving of small mistakes: a missing far jump after setting the protection-enable bit, or a GDT entry with an incorrect base or limit, tends to produce an immediate triple fault and a silent reboot rather than any diagnosable error message, precisely because the machinery that would normally report an error is itself part of what hasn’t been correctly established yet.

  1. ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, Chapter 9: describes the processor state at power-on and the protected-mode transition.
  2. ^ UEFI Specification: describes the UEFI boot flow as an alternative to legacy BIOS/MBR booting.
  • BIOS vs. UEFI: a closer comparison of the two firmware interfaces referenced above.
  • Multiboot: the specification most bootloader-to-kernel handoffs described here actually follow.
  • The A20 Line: the addressing quirk that needs settling during the mode transitions above.
  • Unreal Mode: a technique some hand-written bootloaders use to reach memory above 1 MB before performing the full transition described above.
  • Network Boot (PXE): an alternative to the bootloader stage above being loaded from disk at all.