m³OS docs GitHub

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.

Complete

Learning goal

Understand what a syscall ABI compatibility layer actually involves.

Category

userspace

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 use syscall with Linux numbers.
  • Write a minimal crt0.s that satisfies the System V entry convention.
  • Bundle musl headers and the compiled libc.a in 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

  • futex and pthreads
  • epoll / poll / select
  • signal delivery through the Linux signal ABI
  • /proc filesystem entries
  • dynamic linker support (PT_INTERP, LD_LIBRARY_PATH)
  • mprotect and memory permission changes

Source materials

Important code

Code spotlight cards

Code spotlight

Replace exception-handler context switch with two-phase kill path

kernel/src/arch/x86_64/interrupts.rs See file

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

kernel/src/mm/mod.rs L215-L238

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

kernel/src/task/scheduler.rs See file

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