Scheduler
rou2exOS uses a cooperative/preemptive round-robin scheduler driven by the PIT (Programmable Interval Timer) at 100 Hz (IRQ 0 → interrupt 0x20). Each PIT tick fires scheduler_schedule, which saves the interrupted process's RSP, picks the next runnable process, loads its RSP, and switches CR3 to its page table. The running process can also voluntarily yield by entering a blocking syscall.
Data Structures
struct Scheduler {
processes: [Option<Process>; MAX_PROCESSES], // up to 10 slots
current_pid: usize,
next_free_pid: usize,
}
The scheduler is a global spin::Mutex<Scheduler>, accessed as SCHEDULER. All mutations go through try_lock; if the lock is already held (e.g., a PIT tick fires during a syscall that already holds it), the tick is silently skipped and the current process continues.
Scheduling Algorithm
On every PIT tick:
- Any
Blockedprocess whosesleep_until≤ current tick is transitioned toReady. - A linear scan from
(current_pid + 1) % lenforward finds the next slot that is notNone,Blocked,Crashed, orIdle. If no such slot exists, the interrupted RSP is returned unchanged (current process keeps running). - The outgoing process's RSP is saved into
process.last_rsp. Its status is set toReady(unless it is alreadyBlocked,Crashed,Dead, orIdle). Deadprocesses are reaped (slot set toNone) both eagerly on detection and lazily at the start of the next tick.- The incoming process's status is set to
Running. The TSSRSP0field is updated to the top of the incoming process's kernel stack so that the next ring-3 → ring-0 transition lands on the right stack. - CR3 is written with the incoming process's page table address, flushing the TLB.
- The incoming
last_rspis returned to the naked ISR, which uses it as the new stack pointer foriretq.
Tick and Sleep
The tick counter is maintained by crate::time::acpi::tick() / get_tick_count(), incremented once per PIT interrupt. It has 10 ms resolution.
sleep_current(until_tick) marks the calling process Blocked, stores until_tick in process.sleep_until, and executes hlt. The scheduler wakes the process automatically on the first tick at or after until_tick. The hlt also yields host CPU time to QEMU's event loop so PS/2 input is not starved.
PID vs Slot
PIDs are assigned by a monotonically-incrementing counter (next_free_pid). Slots (processes[0..MAX_PROCESSES]) are reused — a slot can hold processes with different PIDs over time. The kernel stack is pinned to a slot (not a PID) via KSTACK_POOL[slot] so each slot always has a dedicated 32 KiB kernel stack.
Special Processes
| Slot | Name | Mode | Purpose |
|---|---|---|---|
| 0 | kmain |
Kernel | Sentinel/idle — absorbs the boot RSP on the first PIT tick, then loops on hlt |
| 1 | init_rc |
Kernel | Reads INIT.RC from FAT12 root and dispatches each line through the shell command handler; exits when done |
| 2 | clock |
Kernel | Renders a live HH:MM:SS clock in the top-left VGA text buffer corner |
| 3 | shell |
Kernel | Kernel interactive shell; keyboard input loop; PID stored in SHELL_PID |
| 4+ | (userland) | User | ELF processes spawned via run_elf / syscall 0x2A |
Limits
| Resource | Value |
|---|---|
| Max concurrent processes | 10 |
| Kernel stack per process | 32 KiB |
| Message queue depth | 10 messages |
MSG_BUF payload size |
512 bytes |
| Pipe buffer size | ~14 KiB |
| Scheduler tick rate | 100 Hz (10 ms resolution) |
| PID namespace | monotonic usize, never reused |