Writing an OS Kernel from Scratch | Minimal Bootloader — From the Disk’s First Sector to the Kernel Entry Point
Key Insight: After the computer boots, the first instruction executed by the CPU at 0xFFFF0 is neither Windows nor Linux — it’s a piece of code only 446 bytes long. It has no operating system functionality at all. Its only task is: find the next executable program, load it into memory, and jump to it.
1. Problem: After the Computer Boots, Who Runs First?
You press the power button, the CPU resets, and all registers are cleared. But the problem is: Memory has nothing in it. What should the CPU execute?
At this point, the hardware provides a contract: After the CPU powers on, CS:IP is forcibly set to 0xF000:0xFFF0 (x86 architecture), and this address maps to a ROM on the motherboard — the BIOS ROM (now called UEFI Flash). The code in the ROM does two things:
- POST (Power-On Self-Test): Detect basic hardware such as memory, GPU, keyboard
- Traverse boot devices in order (hard drive, USB drive, optical disc…), find the first one that is bootable
After finding one, the BIOS reads the first sector (512 bytes) of that device into memory at 0x7C00, then jumps there to execute.
These 512 bytes are the starting point of the Bootloader.
2. MBR: What’s Hidden in the Disk’s First Sector
The first sector of a hard drive is called the MBR (Master Boot Record). It’s not the operating system’s code; it’s the first link in the boot chain.
The MBR has a fixed structure:
┌─────────────────────────────────────────────────────┐
│ 0x0000 ~ 0x01BD (446 bytes) ← Main boot code │
│ 0x01BE ~ 0x01CD (16 bytes) ← Partition entry 1 │
│ 0x01CE ~ 0x01DD (16 bytes) ← Partition entry 2 │
│ 0x01DE ~ 0x01ED (16 bytes) ← Partition entry 3 │
│ 0x01EE ~ 0x01FD (16 bytes) ← Partition entry 4 │
│ 0x01FE ~ 0x01FF (2 bytes) ← 0x55AA (end marker) │
└─────────────────────────────────────────────────────┘
Total: 512 bytesStructure of a Partition Entry (16 bytes each):
| Offset | Size | Meaning |
|---|---|---|
| 0x00 | 1 | Active flag (0x80 = bootable, 0x00 = not bootable) |
| 0x01 | 3 | CHS start address (old-style cylinder/head/sector) |
| 0x04 | 1 | Partition type (0x07=NTFS, 0x0C=FAT32, 0x83=Linux ext2…) |
| 0x05 | 3 | CHS end address |
| 0x08 | 4 | Start sector (LBA) |
| 0x0C | 4 | Partition size (in sectors) |
The end marker 0xAA55 is key: After reading 512 bytes, the BIOS checks whether the last two bytes are 0x55AA. If not, it considers this disk non-bootable and moves on to the next device.
3. What Does the Main Boot Code Actually Do?
The MBR’s main boot code is only 446 bytes, with limited capabilities. Its core logic is:
- Find the active partition in the partition table (flag = 0x80)
- Read the first sector of that partition into memory (e.g., 0x7C00)
- Verify the 0xAA55 marker
- Jump there to execute
Typical MBR boot code (16-bit real mode):
; Simplified MBR boot code (16-bit real mode)
org 0x7C00 ; BIOS loads the first sector here
start:
mov ax, 0x07C0 ; Set data segment
mov ds, ax
mov es, ax
; Read the first sector of the active partition to 0x8000
mov bx, 0x8000 ; Buffer address
mov dl, [boot_device] ; BIOS-provided boot drive number
mov dh, 0 ; Head number
m... [truncated]
Comments