m³OS docs GitHub

Phase 06

IPC Core

Turn the kernel into a real microkernel by making userspace services communicate through explicit message passing and capabilities.

Complete

Learning goal

Understand why microkernels move services out of ring 0.

Category

foundations

Why this phase matters

Turn the kernel into a real microkernel by making userspace services communicate through explicit message passing and capabilities.

How it works in m3OS

Phase 6 turns m³OS into a real microkernel by wiring up the IPC subsystem that all future userspace servers depend on. By the end of this phase the kernel: Everything from Phase 7 onward — the shell, console server, VFS — sits on top of this foundation. If the IPC contract is clean, those higher layers are straightforward. If it is muddled, nothing above it is trustworthy.

How it builds on earlier phases

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

Implementation outline

  • Define kernel IPC objects and task wait states.
  • Create a per-process capability table with explicit validation.
  • Implement synchronous message transfer first.
  • Add reply-and-wait server flow once the basic path is stable.
  • Add notification objects for interrupts and one-way signals.

Task progress snapshot

26 of 26 tracked tasks are complete.

Documentation targets

  • Documentation deliverables have not been listed yet.

Deferred follow-on work

  • Large page-grant transfers
  • IPC timeouts and cancellation
  • Advanced scheduling policies around IPC

Source materials

Important code

Code spotlight cards

Code spotlight

Define kernel IPC objects for endpoints and notifications

kernel/src/ipc/message.rs L20-L34

Define kernel IPC objects for endpoints and notifications. Why it matters: These are the kernel objects through which all inter-process communication flows — endpoints for synchronous rendezvous, notifications for async signals. Focus on `Message` and `Endpoint` in `kernel/src/ipc/message.rs` while you trace the snippet.

/// A small, register-sized IPC message.
///
/// `label` identifies the operation (similar to a method selector in Mach or
/// a message tag in seL4).  `data` carries up to four 64-bit words of inline
/// payload — enough for most control operations.
///
/// Capability grants are deferred to Phase 7+.  For now, if a server needs to
/// share memory with a client it must use a pre-arranged shared address.
#[derive(Debug, Clone, Copy, Default)]
pub struct Message {
    /// Operation identifier, chosen by convention between sender and receiver.
    pub label: u64,
    /// Inline data payload — up to 4 machine words.
    pub data: [u64; 4],
}

Code spotlight

Add a per-process capability table with validation

kernel/src/ipc/capability.rs L83-L89

Add a per-process capability table with validation. Why it matters: Every IPC operation must go through capability validation — a process can only use handles it legitimately holds, preventing capability forgery. Focus on `CapabilityTable` and `CapHandle` in `kernel/src/ipc/capability.rs` while you trace the snippet.

/// Fixed-size per-process capability table (64 slots).
///
/// `None` means the slot is empty.  Slots are filled on [`insert`][Self::insert]
/// and cleared on [`remove`][Self::remove].
pub struct CapabilityTable {
    slots: [Option<Capability>; Self::SIZE],
}

Code spotlight

Implement blocking recv and send primitives

kernel/src/ipc/endpoint.rs L132-L155

Implement blocking recv and send primitives. Why it matters: Synchronous IPC requires the ability to block the calling task until a partner arrives on the endpoint, then transfer the message and wake both sides. Focus on `recv_msg` and `send_msg` in `kernel/src/ipc/endpoint.rs` while you trace the snippet.

/// Receive a message from an endpoint.
///
/// If a sender is already waiting, dequeue it, wake it (if it used `send`
/// rather than `call`), copy its message, and return the message label.
/// If the endpoint is for a `call`, insert a reply capability into the
/// server's table instead of waking the sender immediately.
///
/// If no sender is waiting, the calling task blocks until one arrives.
///
/// Returns the message label on success, or `u64::MAX` on error.
pub fn recv(receiver: TaskId, ep_id: EndpointId) -> u64 {
    let action = {
        let mut reg = ENDPOINTS.lock();
        let ep = match reg.get_mut(ep_id) {
            Some(e) => e,
            None => return u64::MAX,
        };
        if let Some(pending) = ep.senders.pop_front() {
            Some(pending)
        } else {
            // No sender yet — enqueue self and block.
            ep.receivers.push_back(receiver);
            None
        }

Key files

  • kernel/src/ipc/endpoint.rs — cross-table move — see transfer_cap in kernel/src/ipc/endpoint.rs and
  • kernel/src/arch/x86_64/syscall.rs — authoritative mapping lives in kernel/src/arch/x86_64/syscall.rs, and
  • userspace/syscall-lib — userspace uses it via userspace/syscall-lib. Treat the table above as
  • kernel/src/mm/user_mem.rs — kernel/src/mm/user_mem.rs) to transfer bytes through the caller's page
  • kernel-core/src/ipc/buffer.rs — validate_user_buffer(addr, len) (defined in kernel-core/src/ipc/buffer.rs),
  • kernel/src/ipc/mod.rs — kernel/src/ipc/mod.rs — module overview and syscall dispatcher
  • kernel/src/ipc/notification.rs — kernel/src/ipc/notification.rs — async notification objects
  • kernel/src/ipc/capability.rs — kernel/src/ipc/capability.rs — per-task capability table
  • kernel/src/task/scheduler.rs — kernel/src/task/scheduler.rs — IPC scheduler primitives

How real OSes differ

  • Real microkernels often include more message registers, stronger formal models, priority-aware scheduling interactions, and carefully tuned fast paths.
  • Production systems like seL4 use formal verification to prove IPC correctness.
  • This project keeps the IPC contract small and explicit so the reader can trace every state change during a message exchange.

Success criteria

  • A client can call a server and receive a reply.
  • Invalid capabilities are rejected safely.
  • Server loops use the intended `reply_recv` pattern.
  • IRQ-driven notifications can wake a userspace task.

Related phases

Keep exploring the roadmap