4–5 Minute Detailed Architecture: RustOS (x86_64 Bare-Metal Microkernel)
Target Duration: ~3.5 to 5 minutes (~650–850 spoken words)
Goal: Deliver an end-to-end architectural explanation covering bootloader handoff, CPU control tables, memory management, preemptive context switching, and the filesystem interface.
🎙️ Spoken Script & Architectural Walkthrough
To explain the complete architecture of RustOS, I divide the system into four main stages: CPU bootstrapping and interrupt setup, physical and heap memory management, preemptive scheduling with synchronization, and the I/O and filesystem interface.
Stage 1: CPU Bootstrapping & Hardware Setup
The kernel boots on bare metal under x86_64 in a freestanding no_std environment, meaning there is no C runtime, no standard library, and no OS underneath.
Everything starts in kernel_main where I initialize the core CPU data structures in a strict order:
- Global Descriptor Table (GDT): Even though segmentation is mostly vestigial in 64-bit mode, the GDT is still required to set the 64-bit code segment and load a Task State Segment (TSS). The TSS holds an Interrupt Stack Table pointing to a dedicated emergency stack, ensuring that if a kernel stack overflows, the double fault handler runs on a clean stack rather than causing a fatal triple fault and system reset.
- Interrupt Descriptor Table (IDT): I register 256 interrupt descriptors for CPU exceptions—like breakpoint, double fault, and page fault—as well as external hardware interrupts.
- 8259 PIC Remapping: I remap hardware IRQs to interrupt vectors 32 through 47 to avoid conflicting with x86 CPU exception vectors (0–31). Once remapped, interrupts are safely enabled using the
sti instruction.
(Hook: Deep dive into Topic 01: Boot, GDT & TSS and Topic 02: IDT & PIC)
Stage 2: Physical Memory & Dynamic Heap Allocation
Once hardware interrupts are ready, I initialize memory management using the memory mappings provided by the bootloader:
- The kernel operates with 4-level page tables. I locate the active Level-4 page table using the CR3 register and set up virtual memory mapping.
- For physical memory, I implemented a physical frame allocator that reads the bootloader's memory map to track and hand out usable 4 KiB RAM frames.
- For heap allocation, I mapped a 100 KiB virtual address range to physical memory frames.
- To support dynamic memory allocation in
no_std, I implemented a Fixed-Size Block Allocator registered as the kernel's global allocator. It manages free lists for power-of-two block sizes ranging from 8 bytes up to 2 KiB, with a linked-list fallback for larger requests. This gives $O(1)$ allocation and deallocation for small objects and enables heap-backed types like Box, Vec, and String.
(Hook: Deep dive into Topic 03: 4-Level Paging and Topic 04: Heap & Allocators)
Stage 3: Round-Robin Preemptive Multitasking & Synchronization
With the heap active, I initialize preemptive multitasking:
- Each task is represented by a Process Control Block (PCB) containing its unique ID, task state (
Running, Ready, Blocked), private 8 KiB stack, and saved register state.
- Scheduling is driven by the Programmable Interval Timer (PIT), which fires hardware interrupt 32 at regular periodic intervals.
- The timer interrupt handler is written as a naked assembly function. When the timer fires, the CPU automatically pushes the instruction pointer, flags, and stack pointer. The naked handler pushes all 15 general-purpose registers onto the current task's stack.
- It then calls the Rust scheduler function, which saves the current stack pointer into the running task's PCB, selects the next ready task from a round-robin queue, and returns the next task's stack pointer.
- The assembly handler switches the CPU stack pointer register (
rsp) to the new task's stack, pops all 15 saved registers, sends the End of Interrupt (EOI) signal to the PIC, and executes iretq. This cleanly resumes the new task right where it was interrupted.
- To coordinate concurrent tasks safely, I implemented synchronization primitives from scratch: Spinlocks for interrupt and short critical sections, and Sleep Mutexes and Semaphores for longer operations where contending tasks block instead of burning CPU cycles.
(Hook: Deep dive into Topic 05: Multitasking & Mutex)
Stage 4: VGA Driver, Interactive Shell & In-Memory Filesystem
On top of this multitasking core, I implemented the driver and user-facing layer:
- I wrote a memory-mapped VGA text driver at physical address
0xb8000 supporting 80x25 colored text output, using volatile writes to prevent compiler optimization and interrupt-safe locking to prevent console deadlocks.
- I developed a hierarchical in-memory virtual filesystem (VFS) supporting full CRUD operations: creating, reading, listing, updating, and navigating directories and files.
- Finally, I built an interactive shell running as a preemptive kernel task. It processes PS/2 keyboard inputs via interrupt-driven buffers and exposes commands like
ls, cat, touch, mkdir, cd, and echo to interact with the filesystem in real time.
(Hook: Deep dive into Topic 06: VFS & Shell)