Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Boot Process & Multiboot

What Happens When You Turn a Computer On

When you turn on a computer, it begins executing firmware stored in motherboard ROM. This code performs a power-on self-test, detects available RAM, and pre-initialises the CPU and hardware. Afterwards it looks for a bootable disk and starts booting the OS kernel.

On x86 there are two firmware standards: BIOS (Basic Input/Output System) and the newer UEFI. BIOS is old and outdated, but simple and well-supported on any x86 machine since the 1980s. We use BIOS.

BIOS Boot Sequence

  1. BIOS runs self-test and hardware initialisation
  2. BIOS looks for a bootable disk (checks the boot sector)
  3. BIOS loads the bootloader from the disk
  4. The bootloader switches the CPU from 16-bit real mode → 32-bit protected mode
  5. The bootloader loads the kernel into memory
  6. The bootloader jumps to the kernel entry point

The bootloader also queries the BIOS for memory maps and hardware information and passes that to the kernel.

The Multiboot Standard

To avoid every OS implementing its own bootloader, the Free Software Foundation created the Multiboot standard in 1995. It defines an interface between the bootloader and the OS:

Any Multiboot-compliant bootloader can load any Multiboot-compliant kernel.

The reference implementation is GNU GRUB, the most popular bootloader for Linux systems. To make a kernel Multiboot-compliant, we insert a Multiboot header at the beginning of the kernel file.

What GRUB Does For Us

  1. Scans the first 8KB of the kernel file for the magic number 0x1BADB002
  2. Reads the Multiboot header to understand what the kernel needs
  3. Loads the kernel into memory at address 1MB (0x00100000)
  4. Switches CPU to 32-bit protected mode
  5. Jumps to the kernel’s entry point (_start)

The Multiboot Header

section .multiboot_header
align 4

MULTIBOOT_MAGIC     equ 0x1BADB002
MULTIBOOT_FLAGS     equ 0x00000003
MULTIBOOT_CHECKSUM  equ -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS)

dd MULTIBOOT_MAGIC
dd MULTIBOOT_FLAGS
dd MULTIBOOT_CHECKSUM

Magic Number: 0x1BADB002

This is the special number GRUB searches for. “1 BAD B002” — someone had a sense of humour.

Flags: 0x00000003

  • Bit 0 (value 1): Align loaded modules on page boundaries
  • Bit 1 (value 2): Provide a memory map to the kernel

Checksum

Error detection. The formula ensures: magic + flags + checksum == 0

0x1BADB002 + 0x00000003 + checksum = 0
checksum = -(0x1BADB002 + 0x00000003)

The Stack

Before calling Rust code, the assembly bootloader must set up a stack. The CPU needs a stack for function calls, local variables, and interrupt handlers.

section .bss
align 16

stack_bottom:
    resb 65536       ; 64KB stack
stack_top:

Key properties of the x86 stack:

  • LIFO — Last In, First Out
  • Grows downward — from high addresses to low addresses
  • ESP points to the top — the most recently added item

We reserve 64KB. This was increased from an initial 16KB after we discovered that interrupt handlers need significant stack space.

The Boot Code

section .text
global _start
extern kernel_main

_start:
    mov esp, stack_top  ; Set up stack pointer
    call kernel_main    ; Jump to Rust kernel

.hang:
    cli                 ; Disable interrupts
    hlt                 ; Halt CPU
    jmp .hang           ; Loop forever if we somehow wake up

mov esp, stack_top — Set the Stack Pointer to the top of our reserved region. Remember the stack grows downward, so we start at the top.

call kernel_main — Jump into Rust. From here, the kernel controls everything.

The .hang label handles the case where kernel_main somehow returns (which it shouldn’t — it’s declared -> !). We disable interrupts and halt the CPU.

Key Assembly Instructions Used

InstructionMeaning
mov dest, srcCopy data from src to dest
call fnJump to function (saves return address on stack)
cliClear Interrupts — disable all maskable interrupts
hltHalt CPU until next interrupt
stiSet Interrupts — enable interrupts
pushaPush all general-purpose registers to stack
popaPop all general-purpose registers from stack
iretdReturn from interrupt (32-bit version)

Memory Layout After Boot

0x00100000 (_kernel_start)
    ↓
    [.multiboot]   ← GRUB header (MUST be in first 8KB)
    [.text]        ← Code: _start, kernel_main, handlers
    [.rodata]      ← Constants, strings
    [.data]        ← Initialised globals
    [.bss]         ← Stack, uninitialised data
    ↓
0x001XXXXX (_kernel_end)

The kernel is loaded at 1MB. Everything below 1MB is either BIOS data, BIOS ROM, or hardware memory-mapped regions (like the VGA buffer at 0xB8000).