Architecture & Boot Flow
Detailed walkthrough of system initialisation, hardware setup, and subsystem design.
Boot Flow
GRUB discovers the Multiboot header and jumps to _start in boot.s. From there, a precise initialisation sequence brings the system to a usable state.
GRUB — Multiboot Loader
GRUB reads the Multiboot header (MAGIC=0x1BADB002, FLAGS=ALIGN|MEMINFO, CHECKSUM=-(MAGIC+FLAGS)). Loads the ELF at 1 MiB, jumps to _start.
_start (boot.s) — Stack & GDT
Allocates 8 KiB stack in .bss (16-byte aligned). Clears interrupts (cli). Calls i686_GDT_Initialize to load the 5-entry GDT via lgdt.
safe_switch_protected (boot.s) — Protected Mode
Reads CR0, sets bit 0 (PE), writes back. System is now in 32-bit protected mode. Interrupts re-enabled (sti).
kernel_main (kernel.c)
a) terminal_initialize() — clears VGA buffer, sets default colour.
b) init_idt() — configures PIC, installs 256 ISR gate stubs, loads IDT via lidt.
c) powerup_kernel_i386() — compat layer powerup (see step 5).
powerup_kernel_i386 (kern_powerup.c)
a) Reads CR0 register to confirm protected mode bit.
b) mem$setup_paging() — identity maps 4 MiB, loads CR3, sets CR0.PG.
c) util$self_test() — validates lazy$block copy operations.
d) Sets input_allowed = true, returns KERN$PWRUP.
kern_exit
Raises software interrupt int $55, then returns. If for any reason control reaches here, the CPU halts (cli; hlt; jmp .).
GDT Layout
Five descriptor entries configured in gdt.c. 4 KiB granularity, 32-bit flag. Code and data segments overlap (flat model). Loaded by i686_GDT_Initialize using lgdt.
| Index | Selector | Base | Limit (×4KiB) | Ring | Type | Description |
|---|---|---|---|---|---|---|
| 0 | 0x00 | 0x00000 | 0 | — | NULL | Required null descriptor |
| 1 | 0x08 | 0x00000 | 0xFFFF (4 GiB) | 0 | Code R/X | Kernel code segment |
| 2 | 0x10 | 0x00000 | 0xFFFF (4 GiB) | 0 | Data R/W | Kernel data segment |
| 3 | 0x18 | 0x0FFFF | 0x1FFFE | 3 | Data R/W | User data segment |
| 4 | 0x20 | 0x0FFFF | 0x1FFFE | 3 | Code R/X | User code segment |
GDTEntry Fields
typedef struct {
uint16_t LimitLow; // limit bits 0–15
uint16_t BaseLow; // base bits 0–15
uint8_t BaseMiddle; // base bits 16–23
uint8_t Access; // access byte (ring, type, present)
uint8_t FlagsLimitHi; // limit bits 16–19 | flags (32-bit, 4K gran)
uint8_t BaseHigh; // base bits 24–31
} __attribute__((packed)) GDTEntry;
IDT & PIC
idt.c allocates a 256-entry IDT and initialises both PIC chips via the ICW sequence.
IDT Entry Structure
typedef struct {
uint16_t offset_low; // ISR addr[0:15]
uint16_t segment_selector; // code selector (0x08)
uint8_t alwaysZero;
uint8_t access_gran; // flags: 0x8E = present, 32-bit trap
uint16_t offset_high; // ISR addr[16:31]
} __attribute__((packed)) idt_entry_t;
IDT Pointer
typedef struct {
uint16_t limit; // sizeof(idt_entry_t)*256 - 1
uint32_t base; // address of idt_entries[]
} __attribute__((packed)) idt_ptr_t;
Loaded with lidt [idt_ptr] inside init_idt().
PIC Remapping (init_pic)
The 8259A PIC's default vectors (0–15) conflict with CPU exceptions. They are remapped:
| PIC | Ports | Old Vectors | New Vectors | IRQs |
|---|---|---|---|---|
| Master (PIC1) | CMD=0x20, DATA=0x21 | 0x00–0x07 | 0x20–0x27 | Timer, Keyboard, Cascade, COM2/1, LPT2, Floppy, LPT1 |
| Slave (PIC2) | CMD=0xA0, DATA=0xA1 | 0x08–0x0F | 0x28–0x2F | RTC, I/O, …, Mouse, FPU, HDD |
Interrupt Vectors Summary
| Vector(s) | Source | Handler |
|---|---|---|
| 0x00 – 0x1F | CPU exceptions (div0, debug, NMI, breakpoint, …) | ISR0–ISR31 stubs (idt_hndlr_setup.s) |
| 0x20 (IRQ 0) | PIT Timer | Generic isr_map dispatch |
| 0x21 (IRQ 1) | PS/2 Keyboard | keyboard_read() |
| 0x2E (IRQ 14) | Page Fault | pagefault_hander() |
Paging (vm_watchdog.c)
mem$setup_paging() establishes a simple identity-mapped page table covering the first 4 MiB of physical memory.
// 1. Fill page_table[1024]: entry i = (i * 0x1000) | 3 (present + RW)
// 2. Set page_directory[0] = &page_table | 3
// 3. mov &page_directory → CR3
// 4. Read CR0, set bit 31 (PG), write back → paging enabled
| Structure | Size | Alignment | Description |
|---|---|---|---|
| page_directory[1024] | 4 KiB | 4096 B | Top-level page directory. Entry 0 points to page_table. |
| page_table[1024] | 4 KiB | 4096 B | Maps virtual 0x0–0x3FFFFF → physical 0x0–0x3FFFFF (identity). |
Memory Map (Runtime)
Interrupt Handling Flow
When a hardware interrupt fires, control passes through several layers:
CPU raises interrupt vector N
→ IDT gate[N].offset → ASM stub in idt_hndlr_setup.s
→ saves registers, pushes N
→ calls interrupt_handler(N) [isr.c]
→ if N >= 40: outb(0xA0, PIC_ACK) // EOI to slave
→ if N >= 32: outb(0x20, PIC_ACK) // EOI to master
→ if isr_map[N] != NULL: call isr_map[N]()
→ e.g. keyboard_read() [ttyin.c]
→ e.g. pagefault_hander() [vm_watchdog.c]
ISR Map Registration (isr.c)
void register_routines() {
isr_map[KBD_INTERRUPT] = &keyboard_read; // 0x21
isr_map[PGFLT_INTERRUPT] = &pagefault_hander; // 0x2E
}
VGA Text-Mode Terminal (libk/tty)
The terminal writes directly to the VGA buffer at physical address 0xB8000. Each cell is 2 bytes: character + attribute (fg|bg colour).
Screen Dimensions
| Constant | Value |
|---|---|
| VGA_WIDTH | 80 columns |
| VGA_HEIGHT | 24 rows |
| Buffer size | 80 × 24 × 2 = 3840 bytes |
VGA Entry Format
// bits [15:8] = attribute (color)
// bits [7:0] = ASCII character
uint16_t vga_entry(char c, uint8_t color);
uint8_t vga_entry_color(vga_color fg, vga_color bg);
Key Terminal Functions
| Function | Description |
|---|---|
| terminal_initialize() | Clears buffer, sets default colour, resets cursor to (0,0) |
| terminal_putchar(char c) | Writes char, handles \n, advances cursor, triggers scroll if needed |
| terminal_writestring(const char*) | Calls putchar for each byte until NUL |
| terminal_scroll_down() | Shifts all rows up by one, clears last row |
| terminal_setcolor(uint8_t) | Sets current fg/bg colour for subsequent output |
| terminal_put_uint(d, base, putc) | Prints uint32 in BINARY/OCTAL/DECIMAL/HEX via callback |
libk — Memory Utilities (util.h / memory.c)
Provides a safe block-copy abstraction using lazy$block descriptors.
struct lazy$block {
char* lb$base_addr; // pointer to memory region
uint64_t lb$limit; // length in bytes
};
| Function | Return | Description |
|---|---|---|
| lazy$check_integrity(lb) | size_t | Validates base != NULL and limit != 0. Returns UTIL$SUCCESS or LAZY$* error code. |
| util$blkcpy_weak(dest, src) | size_t | Copies min(dest.limit, src.limit) bytes. Returns UTIL$MEMTRUNC if truncated. |
| util$blkcpy_strong(dest, src) | size_t | Copies only if dest.limit >= src.limit; otherwise fails. |
| util$self_test() | size_t | Runs internal tests; called during kernel powerup. Returns UTIL$SUCCESS or UTIL$FAIL. |
Status Codes
| Macro | Value | Meaning |
|---|---|---|
| UTIL$SUCCESS | 0x1 | Operation succeeded |
| UTIL$FAIL | 0x0 | General failure |
| UTIL$MEMTRUNC | 0x2 | Success but data was truncated |
| LAZY$LIMINV | 0x2 | Block limit is invalid (0) |
| LAZY$BASEINV | 0x4 | Block base address is NULL |
| LAZY$BLOCKINV | 0x6 | Both base and limit invalid |