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 #

✅ Implemented — user/init/src/main.rs

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)

SlotTypeResourceRights
1PmmAllocatorPhysical memory allocatorALL (can allocate frames)
2IoPortbase=0x3F8, size=8 (COM1)ALL (port_in, port_out)
3Processpid=1 (self-reference)ALL (map memory into own VA)
4IoPortbase=0xC000, size=128 (Virtio-Blk BAR 0)ALL (dynamically minted from PCI discovery)

Runtime Phases (Actual)

  1. Serial banner — polled COM1 output via SYS_PORT_IN/SYS_PORT_OUT through IoPort capability (slot 2)
  2. TarFS parse — initrd mapped at 0x1000_0000, USTAR headers walked, entries listed to serial
  3. Heap bootstrap — 1000 pages (4 MiB) at 0x4000_0000 via SYS_ALLOC_MEMORY + SYS_MAP_MEMORY + SYS_DROP_CAP loop
  4. Vec<u64> proof — construct, push 3 elements, verify, drop
  5. Wasm SFI proof — extract hello_wasm.wasm from TarFS, instantiate via wasmi, call add(10, 32)42
  6. 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)
  7. Halt loop — infinite core::hint::spin_loop()

Process Lifecycle (Sprint 8–9)

  1. Spawn childSYS_SPAWN_PROCESS creates empty child with new PML4 + CNode
  2. Delegate capabilitiesSYS_DELEGATE(proc_slot, src_slot, dst_slot) copies caps to child
  3. Load ELF — kernel parses initrd binary, maps PT_LOAD segments into child's VA
  4. Spawn threadSYS_SPAWN_THREAD(proc_slot, user_rip, user_rsp) starts execution

Userspace Library (libmnos) #

✅ Implemented — user/libmnos/src/

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

ModuleContents
syscallRaw syscall4() — inline asm SYSCALL with 4 args (RAX, RDI, RSI, RDX, R10)
ipcsys_send(), sys_recv() — synchronous IPC on endpoint capabilities
iosys_port_out(), sys_port_in() — byte I/O; sys_port_out_32(), sys_port_in_32() — dword I/O via IoPort capability
irqsys_wait_irq() — block until hardware IRQ on IrqLine capability
processsys_spawn_process(), sys_alloc_memory(), sys_map_memory(), sys_delegate(), sys_spawn_thread(), sys_drop_cap()
heapinit_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 #

✅ Implemented — user/serial_drv/ (Sprint 7)

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

  1. ELF loading: serial_drv is a standalone no_std ELF binary in the initrd tar archive
  2. Capability delegation: Init gives serial_drv only the IoPort capability it needs — nothing more
  3. Process isolation: serial_drv runs in Ring 3 with its own PML4 — a bug cannot crash the kernel
  4. 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:

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 #


Wasm Hypervisor (Sprint 10) #

✅ Proven — SFI Hardware Bridge (Wasm → COM1 via Capability Chain)

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

ComponentLocationDescription
hello_wasmapps/hello_wasm/Wasm payload: exports add() + run_guest(), imports host_print(ptr, len), 549 bytes
wasmi engineuser/init/ (dep)wasmi v0.31.2, default-features = false for no_std + alloc
tar_find()user/init/src/main.rsUSTAR TarFS extractor — locates hello_wasm.wasm in the initrd
host_print closureuser/init/src/main.rsRegistered via Linker::func_wrap() — reads Wasm memory, writes to COM1

Execution Flow

  1. Heap bootstrap — 1000 pages (4 MiB) at 0x4000_0000
  2. TarFS extractiontar_find("hello_wasm.wasm") returns 549-byte slice
  3. wasmi EngineEngine::default() creates the interpreter engine on the Ring 3 heap
  4. Module parseModule::new(&engine, wasm_bytes) validates and compiles the Wasm bytecode
  5. Host function registrationlinker.func_wrap("env", "host_print", closure)
  6. Instantiationlinker.instantiate(&mut store, &module)?.start(&mut store)?
  7. Phase 2 proofadd(10, 32)Value::I32(42) — computational isolation
  8. Phase 3 proofrun_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