Skip to content

Bootloaders

A bootloader is the software responsible for the gap between firmware relinquishing control and a kernel being in a state where it can run: locating a kernel image on disk, loading it into memory, performing whatever CPU mode transitions the kernel expects to have already happened, and passing along information (a memory map, boot arguments, module locations) the kernel needs but has no independent way to gather itself. The decision of whether to write one, or rely on an existing one, is among the first practical choices a hobby kernel project makes.

GRUB2 is the most widely used bootloader in the hobby OS development community, implementing both legacy BIOS and UEFI booting, Multiboot (both versions), and filesystem drivers capable of reading a kernel directly off common filesystems without the kernel needing any of that logic itself. A kernel targeting GRUB needs only a correctly placed Multiboot header and a build producing a binary GRUB can load, with no boot sector code, no disk driver, and no filesystem parser, at the cost of depending on an external, general-purpose tool whose behavior and defaults the kernel does not control. Limine, a newer alternative, similarly implements Multiboot-family protocols (and its own, more modern Limine boot protocol) with less legacy baggage and generally simpler configuration, and has become common in newer projects specifically for that reason.

Writing a bootloader from scratch means implementing, at minimum, the earliest boot-sector code firmware itself loads (for BIOS, a 512-byte MBR fitting the loading and signature requirements described under BIOS vs. UEFI), a way to load further code from disk (commonly by relying on BIOS int 0x13 disk services initially, since implementing a real disk driver at this stage is itself a substantial undertaking), and the mode transitions the kernel will otherwise need to perform on its own. This path offers complete control and, for some, is itself the point of the exercise (understanding every step of the boot process firsthand rather than treating it as solved infrastructure), but duplicates work an existing, well-tested bootloader already does correctly, and is considerably more error-prone to get right during the earliest, least-diagnosable stage of a kernel’s execution.

A boot sector’s 512-byte size limit is rarely enough to implement everything a bootloader needs, which is why bootloaders, whether GRUB or a hand-written one, are conventionally split into stages. A minimal stage 1, constrained to the boot sector itself, does the least possible work: enough to locate and load a larger stage 2 from a known disk location, and jump to it. Stage 2, no longer constrained by the 512-byte limit, performs the more substantial work (reading a kernel from a filesystem, parsing its Multiboot header, gathering the memory map, and performing mode transitions) before finally handing off to the kernel itself. This staged structure is exactly what GRUB itself implements internally, abstracted away from a kernel developer who chooses to rely on it rather than write it.

Regardless of whether it is hand-written or an existing tool, every bootloader’s responsibilities converge on the same short list: locate a kernel image (on a raw disk location, or within a filesystem), load it into physical memory at the address it expects, gather whatever environmental information the kernel’s loading protocol requires (a memory map being the most universal example), perform any CPU mode transition the protocol guarantees on the kernel’s behalf, and transfer control with the CPU and any information structures in the state that protocol promises. A kernel choosing to write its own bootloader is, in effect, choosing to implement this list itself rather than delegate it; a kernel choosing GRUB or Limine is choosing to trust that the tool already implements it correctly, in exchange for one less large, boot-critical subsystem to write and debug from nothing.

A kernel that expects to eventually support both a hand-written boot path and an existing bootloader benefits from designing its entry point around a common information structure (a normalized internal representation of the memory map and boot parameters, populated differently depending on which path actually loaded the kernel) rather than writing kernel initialization code that assumes one specific loader’s data format directly, which otherwise has to be duplicated or reworked entirely if the loading path ever changes.

  1. ^ GNU, GRUB Manual: documents GRUB’s own staged internal architecture and supported boot protocols.
  • Multiboot: the protocol most existing bootloaders, GRUB included, use to hand off to a kernel.
  • The Boot Process: the full sequence a bootloader is one stage of.
  • Writing a UEFI Bootloader: an entirely different path, loaded and run directly by firmware with no staged handoff at all.