-
Notifications
You must be signed in to change notification settings - Fork 69
Bootloader
The first MentOS code executed when the system starts.
It is worth being precise about the word "bootloader", because two different things are usually called that:
| Who | What it does | |
|---|---|---|
| External boot loader | GRUB, QEMU's -kernel, or any Multiboot-compliant loader |
Runs from the firmware, starts in 16-bit real mode, switches the CPU to 32-bit protected mode, loads the MentOS image, and jumps to it |
MentOS bootstrap (boot/) |
This page | Runs already in 32-bit protected mode, sets up bootstrap paging, relocates the kernel, and hands over to kmain()
|
MentOS does not ship a real-mode boot sector. boot/src/boot.S opens with bits 32
and never executes a single real-mode instruction.
The MentOS bootstrap is responsible for:
- Minimal CPU setup - Establish a stack and enter C code
- Multiboot Handling - Use the Multiboot header/info provided by GRUB/QEMU
- Kernel Relocation & Paging - Map low memory and relocate the embedded kernel ELF
-
Kernel Transfer - Jump to the kernel entry point (
kmain())
boot/
├── src/
│ ├── boot.S ← Assembly entry point
│ ├── boot.c ← Main bootloader logic
│ └── multiboot.c ← Multiboot spec handling
└── linker/
├── boot.lds ← Bootloader linker script
└── kernel.lds ← Kernel linker script- The firmware starts GRUB (or QEMU loads the image directly); GRUB does the real-mode → protected-mode transition
- GRUB loads
bootloader.binat the address given byboot/linker/boot.lds(0x00100000) - Execution begins at
boot_entry(defined inboot.S) with the CPU in 32-bit protected mode,EAX = 0x2BADB002andEBXpointing at the Multiboot information structure
; boot/src/boot.S
bits 32 ; <- everything below is 32-bit code; no real mode anywhere
section .text
global boot_entry
boot_entry:
; Disable interrupts
cli
; Set up stack
mov esp, stack_top
; Pass arguments to boot_main(magic, header, esp)
push esp ; initial stack pointer
push ebx ; multiboot info structure
push eax ; multiboot magic number
; Call C code
call boot_main
; Halt if boot_main returns
cli
.hang:
hlt
jmp .hangActions:
- Disables interrupts (
cli) - Sets
esptostack_top. The stack is reserved in.bssbyKERNEL_STACK_SIZE equ 0x400000, i.e. 4 MiB - Pushes boot parameters (magic, header, initial stack)
- Calls
boot_main()
In boot/src/boot.c, boot_main() performs the boot-time setup needed to transfer control to the kernel:
- Parses the header of the embedded kernel ELF (
kernel.binis linked intobootloader.binas a binary blob) and walks itsPT_LOADprogram headers to find the kernel's lowest and highest virtual address (__get_kernel_low_high()) - Builds a
boot_info_tstructure (kernel bounds, modules end, memory layout) - Sets up bootstrap paging mappings (identity-maps low memory and maps the kernel at its virtual base)
- Loads CR3 and sets
CR0.PG— paging is on before the kernel gets control - Relocates the embedded kernel ELF to its virtual addresses
- Calls
boot_kernel(stack, entry, &boot_info)to jump to the kernel
Note: GDT/IDT setup happens later inside the kernel (see kernel/src/descriptor_tables/).
The GDT in effect while the bootstrap runs is the one GRUB left behind.
A MentOS-specific bound to be aware of: boot_main() sets
boot_info.lowmem_phy_end = 896 * 1024 * 1024; // 896 MB of low memory maxThe 896 MiB figure is a MentOS implementation limit (it matches the 896 MiB
KERNEL_LOWMEM region declared in boot/linker/kernel.lds), not an architectural
property of x86. Physical memory above it is tracked as "high memory".
// In boot.S: boot_kernel(stack, entry, boot_info)
// Sets ESP, pushes boot_info, and calls kernel entryMentOS uses the Multiboot specification to be compatible with GRUB and other bootloaders.
boot.S is assembled with NASM, so the header is written in NASM syntax:
; boot/src/boot.S
section .multiboot_header
align 4
multiboot_header:
dd MULTIBOOT_HEADER_MAGIC
dd MULTIBOOT_HEADER_FLAGS
dd MULTIBOOT_CHECKSUMConstants (verbatim from boot/src/boot.S):
-
MULTIBOOT_HEADER_MAGIC=0x1BADB002— what the image contains, so the loader can find it -
MULTIBOOT_HEADER_FLAGS=MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO=0x00000003(4 KiB module alignment + memory information).MULTIBOOT_VIDEO_MODE(0x4) is defined in the file but deliberately not requested. -
MULTIBOOT_CHECKSUM=-(MAGIC + FLAGS), so the three fields sum to zero mod 2³² -
MULTIBOOT_BOOTLOADER_MAGIC=0x2BADB002— what the loader leaves inEAX. Do not confuse the two:kmain()checks this second value and refuses to boot if it differs.
The bootloader receives a multiboot_info_t from GRUB (see kernel/inc/multiboot.h):
typedef struct multiboot_info {
uint32_t flags; // Available info
uint32_t mem_lower; // Lower memory (KB)
uint32_t mem_upper; // Upper memory (KB)
uint32_t boot_device; // Boot device
uint32_t cmdline; // Kernel command line
uint32_t mods_count; // Number of modules
uint32_t mods_addr; // Modules address
// ... more fields
} multiboot_info_t;Information Provided:
- Memory map (physical RAM available)
- Boot device
- Kernel command line
- Loaded modules (if any)
- Framebuffer information
__setup_boot_paging() in boot/src/boot.c builds a page directory with two mappings of the
same low physical memory:
- an identity mapping (virtual == physical), so the bootstrap code — which is linked at
0x00100000— keeps executing correctly the instantCR0.PGis set; - a high mapping at the kernel's virtual base (
0xC0000000), so the kernel's link-time addresses become valid.
Paging is then enabled (boot_paging_switch_pgd() loads CR3, boot_paging_enable() clears
CR4.PSE and sets CR0.PG) before the kernel is entered.
Do not confuse this with the kernel's paging subsystem. These bootstrap tables are a
throwaway, statically allocated scaffold. Much later, kmain() calls paging_init(), which
builds the kernel's real, allocator-backed page directory and takes over. Both are "paging",
but they are different code owning different data structures — see Kernel.
Use the CMake targets:
cd build
make bootloader.binbuild/mentos/bootloader.bin contains:
- Bootloader code
- Embedded
kernel.bin(as a linked binary blob)
Key excerpts (see boot/linker/boot.lds for full script):
ENTRY(boot_entry)
. = 0x00100000;
_bootloader_start = .;
.multiboot : {
*(.multiboot_header)
}
.text : { *(.text) }
.rodata : { *(.rodata*) }
.data : { *(.data) }
.bss : { *(.bss*) }
_bootloader_end = .;Links the standalone kernel.bin (used by kernel compilation).
Main bootloader entry point from assembly.
Parameters:
-
magic: Multiboot bootloader magic (0x2BADB002 expected) -
header: Pointer to multiboot information structure -
esp: Initial stack pointer
Actions:
- Build
boot_info_t(kernel bounds, modules end, memory layout) - Set up boot-time paging
- Relocate the kernel ELF image
- Call
boot_kernel()
Creates the bootstrap page tables used during early boot (identity mapping + kernel high mapping).
For each PT_LOAD program header, copies min(filesz, memsz) bytes from the embedded image to
the segment's virtual address, then writes zeroes over the remaining memsz - filesz bytes.
That second step is what makes the kernel's .bss start out zeroed — .bss occupies space in
memory (memsz) but stores nothing in the file (filesz).
Assembly helper that switches the stack pointer and calls the kernel entry.
# Terminal 1
make qemu-gdb
# Terminal 2 (from build/)
gdb --quiet --command=gdb.runAdd debug prints (serial console):
// In boot.c
__debug_puts("[bootloader] Start...\n");objdump -d build/mentos/bootloader.bin | less- GRUB didn't load properly
- Multiboot header is missing or incorrect
- Check
.multiboot_headersection in assembly
- Kernel binary wasn't embedded correctly
- Check linker script
- Verify kernel.bin.o creation
- Paging setup incorrect
- Identity mapping missing
- Check page table entries
- CPU exception during boot
- Usually GDT or paging issue
- Use
make qemu-gdbto catch early
- Kernel - What happens after the bootloader
- Architecture - Overall system structure
- Scheduling - How the kernel runs processes
Previous: Architecture | Next: Kernel →