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.

1

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.

2

_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.

3

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).

4

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).

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.

6

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.

IndexSelectorBaseLimit (×4KiB)RingTypeDescription
00x000x000000NULLRequired null descriptor
10x080x000000xFFFF (4 GiB)0Code R/XKernel code segment
20x100x000000xFFFF (4 GiB)0Data R/WKernel data segment
30x180x0FFFF0x1FFFE3Data R/WUser data segment
40x200x0FFFF0x1FFFE3Code R/XUser 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:

PICPortsOld VectorsNew VectorsIRQs
Master (PIC1)CMD=0x20, DATA=0x210x00–0x070x20–0x27Timer, Keyboard, Cascade, COM2/1, LPT2, Floppy, LPT1
Slave (PIC2)CMD=0xA0, DATA=0xA10x08–0x0F0x28–0x2FRTC, I/O, …, Mouse, FPU, HDD

Interrupt Vectors Summary

Vector(s)SourceHandler
0x00 – 0x1FCPU exceptions (div0, debug, NMI, breakpoint, …)ISR0–ISR31 stubs (idt_hndlr_setup.s)
0x20 (IRQ 0)PIT TimerGeneric isr_map dispatch
0x21 (IRQ 1)PS/2 Keyboardkeyboard_read()
0x2E (IRQ 14)Page Faultpagefault_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
StructureSizeAlignmentDescription
page_directory[1024]4 KiB4096 BTop-level page directory. Entry 0 points to page_table.
page_table[1024]4 KiB4096 BMaps virtual 0x0–0x3FFFFF → physical 0x0–0x3FFFFF (identity).
Warning: Only the first page table (entry 0 in page_directory) is populated. Accessing virtual addresses above 4 MiB will page-fault. pagefault_hander() currently just prints a message and returns.

Memory Map (Runtime)

0x00000000
BIOS / IVT / BDA (real-mode only)
0x000A0000
VGA text buffer @ 0xB8000 (80×24 entries)
0x00100000
Kernel ELF loaded here (1 MiB) — .text, .rodata, .data, .bss
~kernel+size
Stack — 8 KiB (.bss, stack_bottom → stack_top)
(aligned 4K)
page_directory[1024] + page_table[1024] — 8 KiB, 4 KiB-aligned
0x00400000+
Unmapped — page faults here

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

ConstantValue
VGA_WIDTH80 columns
VGA_HEIGHT24 rows
Buffer size80 × 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

FunctionDescription
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
};
FunctionReturnDescription
lazy$check_integrity(lb)size_tValidates base != NULL and limit != 0. Returns UTIL$SUCCESS or LAZY$* error code.
util$blkcpy_weak(dest, src)size_tCopies min(dest.limit, src.limit) bytes. Returns UTIL$MEMTRUNC if truncated.
util$blkcpy_strong(dest, src)size_tCopies only if dest.limit >= src.limit; otherwise fails.
util$self_test()size_tRuns internal tests; called during kernel powerup. Returns UTIL$SUCCESS or UTIL$FAIL.

Status Codes

MacroValueMeaning
UTIL$SUCCESS0x1Operation succeeded
UTIL$FAIL0x0General failure
UTIL$MEMTRUNC0x2Success but data was truncated
LAZY$LIMINV0x2Block limit is invalid (0)
LAZY$BASEINV0x4Block base address is NULL
LAZY$BLOCKINV0x6Both base and limit invalid