m³OS docs GitHub

Phase 05

Userspace Entry

Run the first ring 3 program and prove that privilege separation, page permissions, and basic syscalls work.

Complete

Learning goal

Understand the boundary between ring 0 and ring 3.

Category

userspace

Why this phase matters

Run the first ring 3 program and prove that privilege separation, page permissions, and basic syscalls work.

How it works in m3OS

Phase 5 delivers the first ring-3 program running on m³OS. By the end of this phase the kernel: This is the foundational step for everything in the userspace roadmap. All later phases (IPC, servers, shell) build on the ring-3 execution model established here.

How it builds on earlier phases

This phase builds on Tasking and turns that foundation into Userspace Entry.

Implementation outline

  • Create a process abstraction separate from kernel threads.
  • Build a minimal userspace address space with code and stack mappings.
  • Enter ring 3 through a carefully documented transition path.
  • Install the syscall entry point and argument convention.
  • Add one tiny userspace binary to exercise the path.

Task progress snapshot

19 of 19 tracked tasks are complete.

Documentation targets

  • Documentation deliverables have not been listed yet.

Deferred follow-on work

  • dynamic ELF loading
  • shared libraries
  • full process lifecycle management

Source materials

Important code

Code spotlight cards

Code spotlight

Define a minimal process abstraction

kernel/src/process/mod.rs L9-L15

Define a minimal process abstraction. Why it matters: Processes need their own page tables, file descriptors, and lifecycle state — separate from the kernel task that executes them. Focus on `Process` and `ProcessState` in `kernel/src/process/mod.rs` while you trace the snippet.

/// A minimal userspace process descriptor.
pub struct Process {
    /// Virtual address of the process entry point.
    pub entry: u64,
    /// Virtual address of the process stack top.
    pub stack_top: u64,
}

Code spotlight

Build a user address space

kernel/src/mm/user_space.rs L97-L111

Build a user address space. Why it matters: Userspace code must execute in its own virtual address space with code, stack, and kernel-protected mappings to enforce isolation. Focus on `copy_to_user` in `kernel/src/mm/user_space.rs` while you trace the snippet.

/// Copy `src` bytes into the user code region at `virt_base`.
///
/// The region must already be mapped (e.g. via `map_user_pages`).
/// The bounds check is sized to the **code** region (`USER_CODE_PAGES * 4096`);
/// do not use this helper to write to the stack or any other user region.
pub fn copy_to_user(virt_base: u64, src: &[u8]) -> Result<(), &'static str> {
    let max_bytes = (USER_CODE_PAGES * 4096) as usize;
    if src.len() > max_bytes {
        return Err("copy_to_user: src exceeds mapped user code region");
    }
    // Safety: caller guarantees virt_base is mapped and we've checked the length.
    let dst = unsafe { core::slice::from_raw_parts_mut(virt_base as *mut u8, src.len()) };
    dst.copy_from_slice(src);
    Ok(())
}

Code spotlight

Implement the transition into ring 3

kernel/src/arch/x86_64/mod.rs L28-L51

Implement the transition into ring 3. Why it matters: The ring transition is the security boundary — it drops privilege from ring 0 to ring 3 using iretq with the correct segment selectors. Focus on `enter_userspace` and `iretq` in `kernel/src/arch/x86_64/mod.rs` while you trace the snippet.

/// Transfer execution to ring 3 (userspace).
///
/// Uses `iretq` to atomically switch to user code segment, user stack, and
/// the given entry point with interrupts enabled (RFLAGS.IF = 1).
///
/// # Safety
///
/// * `entry` must be a valid, mapped, executable userspace virtual address.
/// * `user_stack_top` must be a valid, mapped, writable userspace stack
///   address (highest address; stack grows downward).
/// * Must be called after `init()` so that GDT user segments are loaded.
pub unsafe fn enter_userspace(entry: u64, user_stack_top: u64) -> ! {
    use core::arch::asm;
    // Push the five values that `iretq` pops in order:
    //   SS, RSP, RFLAGS, CS, RIP
    // RFLAGS: bit 9 (IF) set → interrupts enabled in userspace.
    asm!(
        "push {ss}",
        "push {rsp}",
        "push {rflags}",
        "push {cs}",
        "push {rip}",
        "iretq",
        ss     = in(reg) u64::from(gdt::user_data_selector().0),

Key files

How real OSes differ

  • Production kernels have richer executable loading, memory permissions, security policies, and per-thread user contexts. This milestone should focus on a tiny, single-purpose userspace program so the privilege transition remains easy to follow.

Success criteria

  • A userspace binary runs and returns control cleanly.
  • Kernel pages are not writable from ring 3.
  • The syscall path is documented and observable through logging.
  • Faulting userspace code is reported without taking down the kernel silently.

Related phases

Keep exploring the roadmap