Sprints 7–10 — Init Process & The God Process
Life outside the kernel — Init holds absolute power.
✅ Complete
Table of contents
Overview #
Sprints 7–9 turned MinimalOS into a real operating system. The kernel loads the init process (PID 1) — the only process that receives capabilities directly from the kernel. Init is the God Process: it parses the initrd TarFS, spawns child processes, delegates capabilities, manages memory from Ring 3, and (as of Sprint 10) runs a dynamic heap allocator. All other processes receive their capabilities from Init.
Init Process #
Role
The init process is the root of the process tree and the God Process. It is the only process that receives capabilities directly from the kernel at boot — all other processes receive their capabilities from init via SYS_DELEGATE.
Boot Capabilities (Actual)
| Slot | Type | Resource | Rights |
|---|---|---|---|
| 1 | PmmAllocator | Physical memory allocator | ALL (can allocate frames) |
| 2 | IoPort | base=0x3F8, size=8 (COM1) | ALL (port_in, port_out) |
| 3 | Process | pid=1 (self-reference) | ALL (map memory into own VA) |
| 4 | IoPort | base=0xC000, size=128 (Virtio-Blk BAR 0) | ALL (dynamically minted from PCI discovery) |
Runtime Phases (Actual)
- Serial banner — polled COM1 output via SYS_PORT_IN/SYS_PORT_OUT through IoPort capability (slot 2)
- TarFS parse — initrd mapped at
0x1000_0000, USTAR headers walked, entries listed to serial - Heap bootstrap — 1000 pages (4 MiB) at
0x4000_0000via SYS_ALLOC_MEMORY + SYS_MAP_MEMORY + SYS_DROP_CAP loop - Vec<u64> proof — construct, push 3 elements, verify, drop
- Wasm SFI proof — extract hello_wasm.wasm from TarFS, instantiate via wasmi, call
add(10, 32)→42 - Virtio-Blk interrogation — read device features + disk capacity (2048 sectors / 1 MB) via 32-bit port I/O through Slot 4 IoPort cap (Sprint 11)
- Halt loop — infinite
core::hint::spin_loop()
Process Lifecycle (Sprint 8–9)
- Spawn child —
SYS_SPAWN_PROCESScreates empty child with new PML4 + CNode - Delegate capabilities —
SYS_DELEGATE(proc_slot, src_slot, dst_slot)copies caps to child - Load ELF — kernel parses initrd binary, maps PT_LOAD segments into child's VA
- Spawn thread —
SYS_SPAWN_THREAD(proc_slot, user_rip, user_rsp)starts execution
Userspace Library (libmnos) #
Purpose
libmnos is a no_std static library that all userspace programs link against. It provides safe Rust wrappers around the raw SYSCALL instruction and a global heap allocator.
Modules
| Module | Contents |
|---|---|
syscall | Raw syscall4() — inline asm SYSCALL with 4 args (RAX, RDI, RSI, RDX, R10) |
ipc | sys_send(), sys_recv() — synchronous IPC on endpoint capabilities |
io | sys_port_out(), sys_port_in() — byte I/O; sys_port_out_32(), sys_port_in_32() — dword I/O via IoPort capability |
irq | sys_wait_irq() — block until hardware IRQ on IrqLine capability |
process | sys_spawn_process(), sys_alloc_memory(), sys_map_memory(), sys_delegate(), sys_spawn_thread(), sys_drop_cap() |
heap | init_heap() — Ring 3 global allocator bootstrap via linked_list_allocator |
Global Allocator (Sprint 10)
libmnos exports a #[global_allocator] backed by linked_list_allocator::LockedHeap. The init_heap() function bootstraps it:
// For each of 1000 pages:
sys_alloc_memory(alloc_slot, scratch_slot); // PMM → MemoryFrame cap
sys_map_memory(proc_slot, scratch_slot, vaddr, 0x01); // Map RW at 0x4000_0000+
sys_drop_cap(scratch_slot); // Free slot for reuse
// After all pages mapped:
HEAP.lock().init(heap_base, heap_size); // 4 MiB usable heap
After init_heap(), all alloc crate types (Vec, Box, String, etc.) work in Ring 3. The 4 MiB heap is large enough to sustain wasmi's memory allocations for Wasm module parsing, instantiation, and execution.
First Userspace Driver #
Serial Console Driver
The first real userspace service — a serial console driver loaded from the initrd TarFS, demonstrating the full capability-based architecture:
sequenceDiagram
participant K as Kernel
participant I as Init (PID 1)
participant S as serial_drv (PID 2)
K->>I: Boot caps (PmmAllocator, IoPort, Process)
I->>K: SYS_SPAWN_PROCESS
K->>I: Process cap (slot N)
I->>K: SYS_DELEGATE (IoPort to child)
I->>K: SYS_SPAWN_THREAD (entry, stack)
K->>S: Ring 3 entry
Note over S: Polled COM1 via SYS_PORT_OUT/IN
What This Demonstrates
- ELF loading: serial_drv is a standalone
no_stdELF binary in the initrd tar archive - Capability delegation: Init gives serial_drv only the IoPort capability it needs — nothing more
- Process isolation: serial_drv runs in Ring 3 with its own PML4 — a bug cannot crash the kernel
- I/O port access: COM1 (0x3F8) accessed via SYS_PORT_OUT/SYS_PORT_IN through IoPort capability
Initramfs #
The init process and initial services are bundled into an initrd that Limine loads alongside the kernel:
- Format: USTAR tar archive (no compression, parsed in Ring 3)
- Contains:
initELF,serial_drvELF,hello_wasm.wasm(384-byte Wasm module) - Loaded by Limine as a module, mapped into Init's address space at
0x1000_0000 - Parsed in Init — USTAR headers walked to find entry names and sizes
TarFS Extraction
Init includes a minimal tar_find() function that walks USTAR headers to locate a named file. This is used to extract hello_wasm.wasm from the initrd for wasmi instantiation.
Address Space Layout (Init, Actual)
block-beta
columns 1
block:stack["0x800000 (top)"]
A["User Stack (64 pages, 256 KiB, RW+NX)"]
end
block:heap["0x4000_0000"]
C["Heap (1000 pages, 4 MiB, RW)"]
end
block:initrd["0x1000_0000"]
D["Initrd TarFS (kernel-mapped, R)"]
end
block:bss[" "]
E[".bss RW (4K-aligned)"]
end
block:data[" "]
F[".data RW (4K-aligned)"]
end
block:rodata[" "]
G[".rodata R (4K-aligned)"]
end
block:text["0x400000 ← ELF base"]
H[".text RX (4K-aligned)"]
end
Dependencies #
- Requires: Sprint 6 (SYSCALL/SYSRET, ELF loader, Ring 3 entry), Sprint 5 (CNode, capabilities)
- Enables: Sprint 9.5 (process teardown, reaper), Sprint 10 (Ring 3 global allocator, Wasm hypervisor) — all complete
Wasm Hypervisor (Sprint 10) #
Architecture
Sprint 10 embeds the wasmi v0.31.2 WebAssembly interpreter directly into Init (PID 1), running entirely in Ring 3. Phase 2 proved computational isolation (add(10, 32) = 42). Phase 3 proved the SFI Hardware Bridge: a Wasm module calls a host function that reads from Wasm linear memory and writes to physical COM1 hardware through the capability system.
Components
| Component | Location | Description |
|---|---|---|
| hello_wasm | apps/hello_wasm/ | Wasm payload: exports add() + run_guest(), imports host_print(ptr, len), 549 bytes |
| wasmi engine | user/init/ (dep) | wasmi v0.31.2, default-features = false for no_std + alloc |
| tar_find() | user/init/src/main.rs | USTAR TarFS extractor — locates hello_wasm.wasm in the initrd |
| host_print closure | user/init/src/main.rs | Registered via Linker::func_wrap() — reads Wasm memory, writes to COM1 |
Execution Flow
- Heap bootstrap — 1000 pages (4 MiB) at
0x4000_0000 - TarFS extraction —
tar_find("hello_wasm.wasm")returns 549-byte slice - wasmi Engine —
Engine::default()creates the interpreter engine on the Ring 3 heap - Module parse —
Module::new(&engine, wasm_bytes)validates and compiles the Wasm bytecode - Host function registration —
linker.func_wrap("env", "host_print", closure) - Instantiation —
linker.instantiate(&mut store, &module)?.start(&mut store)? - Phase 2 proof —
add(10, 32)→Value::I32(42)— computational isolation - Phase 3 proof —
run_guest()→host_print(ptr, len)→ Wasm memory read → COM1 output
The SFI Hardware Bridge (Phase 3)
The complete execution chain from a Wasm instruction to COM1 hardware:
sequenceDiagram
participant W as Wasm Module
participant E as wasmi Engine
participant H as host_print closure
participant M as Wasm Linear Memory
participant L as libmnos (Ring 3)
participant K as Kernel (Ring 0)
participant C as COM1 (0x3F8)
W->>E: call host_print(ptr, len)
E->>H: invoke registered closure
H->>M: Memory::read(ptr, len)
M-->>H: byte buffer
loop For each byte
H->>L: sys_port_out(slot 2, 0x3F8, byte)
L->>K: SYSCALL → SYS_PORT_OUT
K->>K: CNode slot 2 validation
K->>C: OUT 0x3F8, byte
end
Note over C: "Hello from the Wasm Sandbox!"
What This Proves
- Ring 3 heap scalability — 4 MiB heap sustains wasmi's ~11 dependency crates' allocation patterns
- Software Fault Isolation — Wasm modules execute inside wasmi's sandbox, which itself runs in Ring 3 with no kernel privileges
- Host function bridging — Wasm can securely call host functions that access real hardware through capabilities
- Wasm linear memory access — Host reads data from the Wasm module's isolated address space via
Memory::read() - Capability-gated I/O — every byte written to COM1 traverses the full CNode validation path (IoPort slot 2)
- User stack sizing — 64 pages (256 KiB) for wasmi's recursive wasm parser (up from 1 page)