Introduction to Bootloaders and the Boot Process
The journey of an operating system begins long before it displays its graphical interface or executes user applications. It starts with the bootloader, a small, critical piece of code responsible for initializing the system and loading the main operating system kernel into memory. On x86 architectures, this process is intricate, involving a transition from the legacy Real Mode to the more powerful Protected Mode. This article provides an expert-level guide to understanding and writing a minimal x86 bootloader from scratch, covering the essential steps from BIOS interaction to enabling Protected Mode.
Our focus will be on the initial 512-byte sector of a bootable disk, known as the Master Boot Record (MBR), which the BIOS loads into memory. We’ll explore the role of BIOS interrupts, memory addressing in Real Mode, the significance of the A20 line, and the mechanics of setting up and entering Protected Mode.
The BIOS and Real Mode Initialization
BIOS Post and MBR Loading
When an x86 PC powers on, the Central Processing Unit (CPU) starts in Real Mode, a 16-bit operating mode, and executes code from a predefined memory location, typically within the BIOS ROM. The BIOS performs a Power-On Self Test (POST) and then searches for a bootable device. Upon finding one, it reads the first 512-byte sector (the MBR) into memory address 0x7C00 and jumps to it.
At this point, your bootloader code takes control. The CPU is in Real Mode, which means:
- Segment registers (CS, DS, ES, SS) hold 16-bit segment selectors.
- Effective addresses are calculated as
segment * 16 + offset. - Only 1MB of memory is directly addressable (20-bit address bus).
A typical entry point for a bootloader looks like this:
org 0x7c00 ; Bootloader is loaded at 0x7c00jmp short start ; Jump to our main codeNOP ; Padded for boot signature later; Our data here (e.g., boot message)boot_message db "Booting OS...", 0; Main code starts here, after the JMPstart: ; Set up segment registers. They are often undefined or point to the BIOS. mov ax, 0x07c0 mov ds, ax mov es, ax mov ss, ax mov sp, 0x7c00 ; Stack grows downwards from 0x7c00 (or above) ; Print our boot message using BIOS INT 10h mov si, boot_message call print_string; Function to print a null-terminated string using BIOS INT 10hprint_string: mov ah, 0x0e ; Teletype output mode .loop: lodsb ; Load byte from SI into AL cmp al, 0 ; Check for null terminator je .done int 0x10 ; Call BIOS to print character jmp .loop .done: ret; We must pad our bootloader to exactly 512 bytes and end with 0xAA55.times 510 - ($ - $$) db 0dw 0xaa55 ; Boot signature
Using BIOS INT 13h for Disk I/O
To load more code or the kernel, the bootloader uses BIOS interrupt INT 13h. This interrupt provides disk services. For example, to read sectors from the disk:
; Example: Read 1 sector from track 0, head 0, sector 2 into memory at 0x8000 (ES:BX)mov ah, 0x02 ; Function: Read Sectors From Drivemov al, 0x01 ; Number of sectors to readmov ch, 0x00 ; Cylinder (track) 0mov cl, 0x02 ; Sector 2 (1-based)mov dh, 0x00 ; Head 0mov dl, 0x80 ; Drive number (e.g., 0x00 for floppy, 0x80 for HDD)mov bx, 0x0000 ; Offset in ES to store the datamov es, 0x8000 ; Segment where data will be stored (0x8000:0x0000 = 0x80000 absolute)int 0x13 ; Call BIOS disk servicejc disk_error_handler ; Handle read error
Enabling the A20 Line
In Real Mode, the address space is limited to 1MB due to the 20-bit address bus (A0-A19). To access memory beyond 1MB (the
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →