Sprint 7 β Init Process & First Real Program
Life outside the kernel.
π² Planned
Table of contents
Overview #
Sprint 7 is where MinimalOS becomes a real operating system. The kernel loads the init process β the first userspace program β and everything after that happens through capability-mediated IPC. This sprint also creates the userspace library (libmnos) that provides safe Rust wrappers around the raw syscall interface.
Init Process #
Role
The init process is the root of the process tree and the service manager. It's the only process that receives capabilities directly from the kernel at boot β all other processes receive their capabilities from init or its children.
Boot Capabilities
The kernel creates these capabilities and places them in init's capability table:
| Slot | Type | Resource | Rights |
|---|---|---|---|
| 0 | Memory | All usable RAM | Read, Write, Grant |
| 1 | IPC Endpoint | Kernel log | Send |
| 2 | Process | Self | All |
| 3 | Interrupt | All unmasked IRQs | Receive, Grant |
Responsibilities
- Spawn child processes β load service binaries from the initramfs
- Distribute capabilities β give each service the minimum capabilities it needs
- Service registration β maintain a nameβendpoint directory ("serial_driver" β endpoint #7)
- Service lookup β respond to queries: "where is the VFS service?"
- Crash recovery β restart failed services
Service Registration Protocol
graph LR
subgraph SD["Init Process Service Directory"]
S1["serial_driver"] --> E1["endpoint_7"]
S2["vfs_service"] --> E2["endpoint_12"]
S3["network_stack"] --> E3["endpoint_15"]
end
Services register with init via IPC:
β IPC_SEND(init_endpoint, { type: REGISTER, name: "serial_driver", endpoint: my_endpoint })
β IPC_REPLY({ status: OK })
Clients look up services:
β IPC_SEND(init_endpoint, { type: LOOKUP, name: "serial_driver" })
β IPC_REPLY({ status: OK, endpoint: serial_endpoint_cap })
Userspace Library (libmnos) #
Purpose
libmnos is a static library that userspace programs link against. It provides safe Rust abstractions over the raw SYSCALL interface.
Syscall Wrappers
// Raw syscall (unsafe)
unsafe fn syscall3(nr: u64, arg1: u64, arg2: u64, arg3: u64) -> i64 {
let ret: i64;
asm!(
"syscall",
in("rax") nr,
in("rdi") arg1,
in("rsi") arg2,
in("rdx") arg3,
out("rcx") _, // clobbered by SYSCALL
out("r11") _, // clobbered by SYSCALL
lateout("rax") ret,
);
ret
}
// Safe wrapper
pub fn ipc_send(endpoint: CapSlot, msg: &IpcMessage) -> Result<(), IpcError> {
let ret = unsafe { syscall3(SYS_IPC_SEND, endpoint.0 as u64, msg as *const _ as u64, 0) };
match ret {
0 => Ok(()),
e => Err(IpcError::from_code(e)),
}
}
IPC Helpers
// High-level RPC call
pub fn rpc_call<Req: Serialize, Resp: Deserialize>(
endpoint: CapSlot,
request: &Req,
) -> Result<Resp, IpcError> {
let msg = IpcMessage::encode(request);
let reply = ipc_call(endpoint, &msg)?;
Ok(reply.decode()?)
}
Memory Allocation
Userspace gets its own heap allocator that requests pages from the kernel:
pub fn alloc_pages(count: usize) -> Result<*mut u8, MemError> {
let addr = unsafe { syscall2(SYS_MEM_ALLOC, count as u64, 0) };
if addr < 0 { Err(MemError::OutOfMemory) }
else { Ok(addr as *mut u8) }
}
First Userspace Driver #
Serial Console Driver
The first real userspace service β a serial console driver that demonstrates the full capability-based architecture:
sequenceDiagram
participant K as Kernel
participant I as Init Process
participant S as Serial Driver
K->>I: Boot capabilities
I->>S: Spawn + caps (IRQ 4, MMIO, IPC endpoint)
Note over K,S: Runtime operation
K-->>S: IRQ 4 fires β notify
S->>S: Read serial port, process input
S->>I: IPC: echo to output
What This Demonstrates
- Interrupt routing: IRQ 4 (COM1) is routed to the serial driver via an interrupt capability
- MMIO access: The driver has a memory capability for the serial port's I/O range
- Process isolation: The driver runs in Ring 3 β a bug in the driver can't crash the kernel
- IPC communication: The serial driver communicates with other processes via IPC
- Capability delegation: Init gives the serial driver exactly the capabilities it needs β nothing more
Initramfs #
The init process and initial services are bundled into an in-memory filesystem (initramfs) that Limine loads alongside the kernel:
- Format: tar archive (simple, no compression needed for small images)
- Contains: init binary, serial driver binary, other initial services
- Loaded by Limine as a module
- Init unpacks it to find and load service binaries
Dependencies #
- Requires: Sprint 6 (SYSCALL/SYSRET, ELF loader, Ring 3 entry)
- Enables: Future milestones (VFS, disk drivers, networking β all built on this foundation)