moonshot

technical

chrone

chrone is a preemptive, round-robin scheduler. tasks do not yield, the timer takes the CPU from them. a programmable interval timer fires on a fixed period, every tick lands in the kernel's interrupt path, and every few ticks chrone switches which task the CPU returns to.

round-robin is the simplest fair rule there is. the runnable tasks form a ring, and the CPU is handed to each in turn, one after the next, wrapping back to the first once the last has had its slice. no task is favoured over another and none can hold the CPU longer than its turn, because the turn ends whether the task is finished or not, the timer sees to that.

the task table

tasks live in a fixed-size table, allocated from the kernel heap when the scheduler starts. c0 has no structs, so the table is a set of parallel arrays indexed by task id, the task's saved stack pointer, its state (free, runnable, or exited), how many times it has been scheduled, a counter the task itself increments as it works, the root of its page table (a ring-3 process gets its own, kernel tasks share the identity map), and, for ring-3 processes spawned with a dedicated kakel window, the id of that window.the same parallel-array idiom recurs everywhere in the kernel that a richer language would use a struct.

how a switch happens

the switch mechanism leans entirely on how x86_64 handles interrupts. when the timer fires, the CPU pushes the interrupted task's full return frame onto that task's own stack, and the kernel's interrupt entry pushes the rest of the registers after it. to switch tasks, chrone only has to remember where that stack pointer ended up, pick the next runnable slot in the table (wrapping around, skipping exited tasks), and hand the interrupt-return instruction the saved stack pointer of the chosen task instead. the same return path serves a brand-new task too, spawning crafts a hand-built frame on a fresh stack page that looks exactly as if the task had already been interrupted once.one restore path for both first launch and every later resume. there is no special case, which is much of why the mechanism is trustworthy.

spawn, exit, kill

task_spawn takes an entry function, claims the next free slot, gives it one page of stack, and marks it runnable. it is called at boot for the first tasks and at runtime by the shell, skalman. a task ends one of two ways. task_exit marks its own slot exited and parks until the next tick carries the CPU away for good, and kill marks someone else's slot exited, no cooperation from the target needed, the scheduler simply never picks it again.

the reaper

an exited task cannot free its own stack, it is standing on it. so at the top of every tick a reaper runs in whichever task is current, and returns the stack pages of exited tasks (other than the current one) to jenna's page allocator. spawn, run, exit or kill, reap, reuse. the full lifecycle of a task's memory is closed, and the same physical page has been watched cycling through it.

the quantum

a task runs for one timer tick, about ten milliseconds, before it is switched out. the quantum is that short because of what a longer one does to anything that moves on screen. with four runnable tasks and a quantum of a tenth of a second, every task runs for a tenth of a second and then sits frozen for three tenths, so a program's frames arrive in a burst about ten milliseconds apart followed by a gap of three hundred milliseconds with nothing at all in between. on screen that is a sprite that sweeps forward and then stalls, over and over, and no amount of tuning inside the program can repair it, because nothing can be drawn during the three tenths of a second it is not running.

the frame rate does not depend on the quantum at all. a task gets one tick in every four when there are four of them, whatever the quantum, so the program draws about twenty-five frames a second either way. only the spacing changes. at one tick per turn the same twenty-five frames are handed out evenly, forty milliseconds apart, instead of arriving in a clump and then nothing. the cost of switching that often is one pick and one stack-pointer swap per tick, which at a hundred hertz is not worth measuring. the one genuinely expensive thing that rides a timer, the status bar, a full-width fill and forty-odd glyphs, keeps its own slower counter.