m³OS docs GitHub

Phase 04

Tasking

Introduce multiple concurrent execution contexts in the kernel and make timer-driven preemption visible and understandable.

Complete

Learning goal

Understand what a task context actually contains.

Category

foundations

Why this phase matters

Introduce multiple concurrent execution contexts in the kernel and make timer-driven preemption visible and understandable.

How it works in m3OS

Phase 4 introduces cooperative multi-tasking to the kernel. Each task is an independent unit of execution with its own stack. The kernel switches between tasks by saving and restoring a small register frame — the context — without involving the CPU’s hardware task-switching mechanism (which is too heavy-weight for a microkernel).

How it builds on earlier phases

This phase builds on Interrupts and turns that foundation into Tasking.

Implementation outline

  • Define the task model and saved register layout.
  • Create kernel stacks for spawned tasks.
  • Implement the assembly context switch with a narrow ABI.
  • Add a ready queue and a simple scheduler.
  • Trigger rescheduling from timer interrupts.

Task progress snapshot

18 of 18 tracked tasks are complete.

Documentation targets

  • Documentation deliverables have not been listed yet.

Deferred follow-on work

  • priorities and deadline scheduling
  • sleep queues and timers beyond the basic tick
  • SMP-aware run queues

Source materials

Important code

Code spotlight cards

Code spotlight

Define the kernel task structure and task states

kernel/src/task/mod.rs L80-L92

Define the kernel task structure and task states. Why it matters: Every schedulable unit needs a descriptor holding its saved registers, state, stack pointer, and metadata. Focus on `Task` and `TaskState` in `kernel/src/task/mod.rs` while you trace the snippet.

pub struct Task {
    /// Unique task identifier — not yet read outside this module.
    #[allow(dead_code)]
    pub id: TaskId,
    /// Human-readable name — not yet read outside this module.
    #[allow(dead_code)]
    pub name: &'static str,
    pub state: TaskState,
    /// RSP saved by `switch_context` when this task is not running.
    pub saved_rsp: u64,
    /// Owns the allocated kernel stack — dropped when the `Task` is dropped.
    _stack: Box<[u8]>,
}

Code spotlight

Add a round-robin scheduler and ready queue

kernel/src/task/scheduler.rs L53-L64

Add a round-robin scheduler and ready queue. Why it matters: The scheduler determines which task runs next and drives all cooperative and preemptive multitasking. Focus on `Scheduler` and `Ready` in `kernel/src/task/scheduler.rs` while you trace the snippet.

struct Scheduler {
    tasks: Vec<Task>,
    /// Index of the last non-idle task that was dispatched (for round-robin).
    last_run: usize,
    /// Index of the task currently holding the CPU, or `None` while the
    /// scheduler loop itself is running.
    current: Option<usize>,
    /// Index of the dedicated idle task in `tasks`, registered via
    /// [`spawn_idle`].  The idle task is excluded from the normal round-robin
    /// rotation and is selected only when no non-idle task is `Ready`.
    idle_task: Option<usize>,
}

Code spotlight

Trigger scheduling from the timer interrupt

kernel/src/arch/x86_64/interrupts.rs L121-L128

Trigger scheduling from the timer interrupt. Why it matters: Timer-driven preemption ensures no single task monopolizes the CPU, which is the foundation of a multitasking OS. Focus on `timer_handler` and `signal_reschedule` in `kernel/src/arch/x86_64/interrupts.rs` while you trace the snippet.

extern "x86-interrupt" fn timer_handler(_stack_frame: InterruptStackFrame) {
    TICK_COUNT.fetch_add(1, Ordering::Relaxed);
    crate::task::signal_reschedule();
    unsafe {
        PICS.lock()
            .notify_end_of_interrupt(InterruptIndex::Timer as u8);
    }
}

Key files

How real OSes differ

  • Mature schedulers handle priorities, CPU affinity, latency targets, and many blocking sources. A toy round-robin scheduler is intentionally unsophisticated because it makes context switching and task states easy to inspect and reason about.

Success criteria

  • At least two kernel tasks can run and interleave output.
  • Register state survives task switches.
  • The idle task runs when there is no ready work.
  • Scheduler behavior is simple enough to explain from logs.

Related phases

Keep exploring the roadmap