Phase 12
POSIX Compat
Implement enough Linux-compatible syscalls and bundle musl libc so that ordinary C programs compiled on the host can run unmodified inside the OS.
Learning goal
Understand what a syscall ABI compatibility layer actually involves.
Category
userspace
Components touched
Builds on
Unlocked by this phase
Why this phase matters
Implement enough Linux-compatible syscalls and bundle musl libc so that ordinary C programs compiled on the host can run unmodified inside the OS.
How it works in m3OS
The roadmap entry defines the learning scope for this phase; a deeper implementation doc has not been matched yet.
How it builds on earlier phases
This phase builds on Process Model and turns that foundation into POSIX Compat.
Implementation outline
- Audit which syscall numbers Linux assigns to the ~40 musl-required calls.
- Add a second dispatch table in the syscall gate that maps Linux numbers to internal kernel functions. The existing custom ABI remains untouched.
- Compile musl on the host with
--target x86_64-unknown-none, patching only the syscall wrapper stubs to usesyscallwith Linux numbers. - Write a minimal
crt0.sthat satisfies the System V entry convention. - Bundle musl headers and the compiled
libc.ain the disk image. - Validate with a “hello world” C binary: compile on host, copy to image, run inside OS.
Task progress snapshot
44 of 44 tracked tasks are complete.
Documentation targets
- Documentation deliverables have not been listed yet.
Deferred follow-on work
futexand pthreadsepoll/poll/select- signal delivery through the Linux signal ABI
/procfilesystem entries- dynamic linker support (
PT_INTERP,LD_LIBRARY_PATH) mprotectand memory permission changes
Source materials
Important code
Code spotlight cards
Code spotlight
Replace exception-handler context switch with two-phase kill path
Replace exception-handler context switch with two-phase kill path. Why it matters: Context-switching inside an exception handler violates the interrupt-handler contract and can corrupt kernel state. Focus on `KILL_PENDING` and `sys_exit(-11)` in `kernel/src/arch/x86_64/interrupts.rs` while you trace the snippet.
Code spotlight
Free old address space on execve success
Free old address space on execve success. Why it matters: Without freeing the old page tables, every execve leaks the entire previous address space. Focus on `free_process_page_table` and `free_process_page_table(cr3_phys)` in `kernel/src/mm/mod.rs` while you trace the snippet.
/// Free all user-space page table frames for the given PML4 physical address.
///
/// Walks PML4 indices 0–255 (user half), frees every mapped user-accessible
/// physical frame, and frees the page-table structure frames themselves.
///
/// Frame reclamation is a stub in the bump allocator (no-op); the real benefit
/// today is correctness — the function documents the ownership transfer and
/// will become fully effective once Phase 13 adds a free list.
///
/// # Safety
///
/// `cr3_phys` must be the physical address of a valid, now-unreachable PML4
/// that is no longer loaded in CR3. No other code may access the page table
/// after this call.
#[allow(dead_code)]
pub fn free_process_page_table(cr3_phys: u64) {
use x86_64::structures::paging::{PageTable, PageTableFlags};
let phys_off = VirtAddr::new(phys_offset());
// SAFETY: cr3_phys is a valid PML4 frame being freed. The caller guarantees
// it is no longer active (not in CR3) and has exclusive ownership.
unsafe {
let pml4: &PageTable = &*(phys_off + cr3_phys).as_ptr::<PageTable>();
for p4 in 0usize..256 {
let p4e = &pml4[p4]; Code spotlight
Reap dead kernel tasks from scheduler
Reap dead kernel tasks from scheduler. Why it matters: Without reaping, dead tasks accumulate in the scheduler queue indefinitely. Focus on `TaskState::Dead` in `kernel/src/task/scheduler.rs` while you trace the snippet.
Key files
- kernel/initrd/ — for each C source in userspace/. The resulting ELF is placed in kernel/initrd/
- kernel/src/fs/ramdisk.rs — and embedded via include_bytes! in kernel/src/fs/ramdisk.rs.
- kernel/src/mm/user_mem.rs — Both functions in kernel/src/mm/user_mem.rs follow the same pattern:
How real OSes differ
- Real Linux-compatible layers (WSL, FreeBSD's Linux ABI, Darling) implement hundreds of syscalls and handle subtleties like signal masks, `/proc` reads, `epoll`, and `futex`-based threading. This phase targets only the subset needed for a single-threaded C compiler and basic file utilities. Threading and `futex` are deferred.
Success criteria
- A C program that calls `printf`, `malloc`, `fopen`, and `exit` runs correctly.
- Programs compiled with `cc -static -o hello hello.c` (targeting musl) execute inside the OS without modification.
- Standard I/O reaches the console server through the syscall path.
- The existing custom syscall ABI still works for native Rust userspace code.
Related phases
Keep exploring the roadmap
Phase 11
CompleteProcess Model
Load and execute arbitrary userspace binaries from disk as isolated ring-3 processes, with a proper lifecycle: spawn, run, wait, exit.
Phase 13
CompleteWritable FS
Allow processes to create, write, and delete files so the OS has persistent and ephemeral mutable storage. A RAM-backed tmpfs provides scratch space; FAT32 gains write support for persistence across reboots.