Files
SUB-OS/kernel/sched.c
T
Claude a14a7c79b0 feat(sched): Weave tiered scheduler with real kernel-thread context switching
Replace the state-relabel-only round-robin with the SUB-OS "Weave"
scheduler backed by a genuine callee-saved context switch. Threads now
actually run on their own kernel stacks instead of merely having their
state field rewritten.

What was implemented:
- sub_ctx_switch(): per-arch assembly (arch/x86_64/cpu/switch.asm,
  arch/aarch64/cpu/switch.S, arch/armv8i/cpu/switch.S) that saves/restores
  the callee-saved register set (+ flags on x86_64) on each thread's own
  stack, plus a first-run trampoline (sub_thread_trampoline) that launches
  a freshly forged thread frame with entry/arg in callee-saved registers.
- task_forge_frame(): builds the initial saved-context frame per arch so
  the very first switch into a new thread lands on the trampoline; task_t
  gains a pointer-sized ctx_sp so the same struct works on 64- and 32-bit.
- Weave policy (kernel/sched.c): WEAVE_NLANES priority lanes, always
  dispatch the lowest non-empty lane round-robin; a task that yields
  voluntarily is woven UP a lane (interactive), one preempted after
  burning its quantum is woven DOWN (CPU-bound). Cooperative switching
  (sched_yield / task_exit) is always live; timer-driven preemption is
  gated behind sched_set_preempt() and left disarmed for now.
- task_exit() now switches to the next runnable thread via
  sched_exit_current() instead of spinning/halting.
- Boot-time weave_selftest() (x86_64) and sched_dump() diagnostics.

How it was verified:
- Builds clean for all three targets: x86_64 (CROSS_COMPILE=x86_64-linux-gnu-,
  RUSTC="rustc --target x86_64-unknown-none"), aarch64, armv8i. No new
  warnings; the new x86_64 asm carries a .note.GNU-stack section.
- Booted x86_64 under QEMU (TCG): the self-test runs three cooperative
  kernel threads to completion on separate stacks with the expected
  1->2->3 round-robin interleave, reports "20 real context switches,
  3/3 workers exited", returns cleanly to the boot thread, and boot
  proceeds normally to ring-3 init and the shell. No fault/panic.

Left unverified:
- The aarch64/armv8i sub_ctx_switch frames build and link but are not yet
  boot-tested on their QEMU targets (tracked in docs/AUTODEV_ROADMAP.md).
- Timer preemption path is implemented but disarmed; exited kernel threads
  currently leak their stack (reaper is next in the backlog).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Jk6K69yGS7zYgvoWd6nJxQ
2026-08-25 08:16:37 +00:00

270 lines
8.8 KiB
C

#include <kernel/sched.h>
#include <kernel/task.h>
#include <kernel/sync.h>
#include <kernel/printk.h>
#include <arch/arch.h>
/*
* SUB-OS "Weave" scheduler
* ========================
* See include/kernel/sched.h for the design overview. In short: WEAVE_NLANES
* priority lanes, always dispatch the lowest non-empty lane round-robin, and
* let a task's lane float based on how it gives up the CPU -- volunteers are
* woven up (toward lower latency), quantum-burners are woven down.
*
* The switch itself is real: sub_ctx_switch() saves and restores callee-saved
* state on each thread's own kernel stack. weave_current always names the
* thread physically executing; the invariant is maintained by having the
* switcher publish the target as current *before* the register swap.
*/
extern void sub_ctx_switch(void** save_sp, void* load_sp);
/* Quantum (in ticks) granted when a task (re)enters each lane. Lower lanes are
* for interactive work: short quanta, quick rotation. Higher lanes are for
* CPU-bound work: longer quanta, fewer switches. */
static const uint32_t weave_quantum[WEAVE_NLANES] = { 2, 4, 8, 16 };
static struct list_head weave_lane[WEAVE_NLANES];
static task_t* weave_current = NULL;
static spinlock_t weave_lock = SPINLOCK_INIT;
static bool weave_preempt = false; /* timer-driven preemption armed? */
static uint32_t weave_nready = 0; /* runnable tasks parked in lanes */
static uint64_t weave_switches = 0; /* lifetime context switches */
/* ---- lane primitives (call with weave_lock held) ------------------------ */
static void weave_enqueue(task_t* t) {
if (t->weave_lane >= WEAVE_NLANES)
t->weave_lane = WEAVE_NLANES - 1;
list_add_tail(&t->list, &weave_lane[t->weave_lane]);
weave_nready++;
}
static task_t* weave_pick(void) {
for (int l = 0; l < WEAVE_NLANES; l++) {
if (!list_empty(&weave_lane[l]))
return list_entry(weave_lane[l].next, task_t, list);
}
return NULL;
}
/* Publish `next` as the running thread and swap register/stack context.
* Returns (into the *previous* thread) only when it is later switched back in.
* Must be called WITHOUT weave_lock held -- the switched-in thread will resume
* wherever it last called into the switch and expects the lock free. */
static void weave_switch_to(task_t* next) {
task_t* prev = weave_current;
if (next == prev)
return;
next->state = TASK_STATE_RUNNING;
next->sched_runs++;
weave_switches++;
weave_current = next;
task_set_current(next);
sub_ctx_switch(&prev->ctx_sp, next->ctx_sp);
/* control resumes here as `prev` on some later switch-in */
}
/* ---- public API --------------------------------------------------------- */
void sched_init(void) {
for (int l = 0; l < WEAVE_NLANES; l++)
INIT_LIST_HEAD(&weave_lane[l]);
weave_nready = 0;
weave_switches = 0;
weave_preempt = false;
task_init_idle(); /* sets current_task = swapper/0 */
weave_current = task_current();
weave_current->weave_lane = 0;
weave_current->weave_credit = weave_quantum[0];
weave_current->weave_on = 0; /* idle isn't part of the rotation yet */
}
void sched_add_task(task_t* task) {
if (!task) return;
spin_lock(&weave_lock);
task->weave_lane = 0;
task->weave_credit = weave_quantum[0];
task->weave_on = 1;
task->state = TASK_STATE_READY;
weave_enqueue(task);
spin_unlock(&weave_lock);
}
/* A node is currently threaded into a lane iff its links are non-NULL and do
* not point back at itself (list_del() NULLs them; INIT_LIST_HEAD() self-links
* an idle node). Only such a node may be unlinked. */
static bool weave_linked(task_t* t) {
return t->list.next != NULL && t->list.next != &t->list;
}
void sched_remove_task(task_t* task) {
if (!task) return;
spin_lock(&weave_lock);
if (weave_linked(task)) {
list_del(&task->list);
if (weave_nready) weave_nready--;
}
task->weave_on = 0;
spin_unlock(&weave_lock);
}
void sched_join(void) {
if (weave_current) weave_current->weave_on = 1;
}
void sched_leave(void) {
if (weave_current) weave_current->weave_on = 0;
}
void sched_set_preempt(bool armed) {
weave_preempt = armed;
}
/*
* Common rotation core. `voluntary` distinguishes a cooperative yield (the
* caller is rewarded, woven up a lane) from an involuntary/preemptive switch
* (the caller is woven down). Picks the next thread, requeues the caller if it
* is still a participant, and performs the switch.
*/
static void weave_rotate(bool voluntary) {
spin_lock(&weave_lock);
task_t* cur = weave_current;
task_t* next = weave_pick();
if (!next) {
spin_unlock(&weave_lock);
return; /* nobody else runnable: keep going */
}
list_del(&next->list);
if (weave_nready) weave_nready--;
if (cur && cur->weave_on &&
(cur->state == TASK_STATE_RUNNING || cur->state == TASK_STATE_READY)) {
if (voluntary) {
if (cur->weave_lane > 0) cur->weave_lane--; /* weave up */
} else {
if (cur->weave_lane < WEAVE_NLANES - 1) cur->weave_lane++; /* down */
}
cur->weave_credit = weave_quantum[cur->weave_lane];
cur->state = TASK_STATE_READY;
weave_enqueue(cur);
}
next->weave_credit = weave_quantum[next->weave_lane];
spin_unlock(&weave_lock);
weave_switch_to(next);
}
void sched_yield(void) {
weave_rotate(true);
}
void sched_schedule(void) {
weave_rotate(false);
}
void sched_tick(void) {
task_t* cur = weave_current;
if (!cur || cur->pid == 0)
return; /* idle thread isn't charged */
if (cur->weave_credit > 0)
cur->weave_credit--;
if (cur->weave_credit == 0 && weave_preempt)
sched_schedule(); /* quantum spent -> preempt */
}
/* Retire the running thread: switch to the next runnable one and never come
* back. If the run rotation is empty we simply return so the caller can halt. */
void sched_exit_current(void) {
spin_lock(&weave_lock);
task_t* next = weave_pick();
if (next) {
list_del(&next->list);
if (weave_nready) weave_nready--;
}
spin_unlock(&weave_lock);
if (!next)
return;
void* graveyard; /* dead context's SP goes nowhere */
next->state = TASK_STATE_RUNNING;
next->sched_runs++;
next->weave_credit = weave_quantum[next->weave_lane];
weave_switches++;
weave_current = next;
task_set_current(next);
sub_ctx_switch(&graveyard, next->ctx_sp);
/* unreachable */
}
/* ---- diagnostics -------------------------------------------------------- */
void sched_dump(void) {
spin_lock(&weave_lock);
printk(ANSI_BRIGHT_CYAN "=== Weave Scheduler ===\n" ANSI_RESET);
printk(" running: %s (pid %d, lane %u) switches: %u ready: %u\n",
weave_current ? weave_current->name : "?",
weave_current ? (int)weave_current->pid : -1,
weave_current ? (unsigned)weave_current->weave_lane : 0,
(unsigned)weave_switches, (unsigned)weave_nready);
for (int l = 0; l < WEAVE_NLANES; l++) {
printk(" lane %d:", l);
task_t* pos;
int n = 0;
list_for_each_entry(pos, &weave_lane[l], list) {
printk(" %s", pos->name);
n++;
}
if (!n) printk(" (empty)");
printk("\n");
}
spin_unlock(&weave_lock);
}
/* ---- boot-time context-switch self-test --------------------------------- */
static volatile int weave_test_exits = 0;
static void weave_worker(void* arg) {
long id = (long)(uintptr_t)arg;
for (int i = 0; i < 4; i++) {
task_t* self = task_current();
printk(" [weave] worker %d slice %d (lane %u, runs %u)\n",
(int)id, i, (unsigned)self->weave_lane, (unsigned)self->sched_runs);
sched_yield(); /* cooperatively hand off the CPU */
}
printk(" [weave] worker %d done\n", (int)id);
weave_test_exits++;
/* fall off the end -> trampoline -> task_exit -> switch away, never return */
}
void weave_selftest(void) {
printk(ANSI_BRIGHT_CYAN
"WEAVE: cooperative context-switch self-test\n" ANSI_RESET);
uint64_t before = weave_switches;
weave_test_exits = 0;
task_create("weave-a", weave_worker, (void*)(uintptr_t)1, 0);
task_create("weave-b", weave_worker, (void*)(uintptr_t)2, 0);
task_create("weave-c", weave_worker, (void*)(uintptr_t)3, 0);
/* Join the rotation so exiting workers can switch back to us, then drive
* the whole batch to completion by repeatedly yielding. */
sched_join();
while (weave_test_exits < 3)
sched_yield();
sched_leave();
printk(ANSI_BRIGHT_GREEN
"WEAVE: self-test OK -- %u real context switches, 3/3 workers exited\n"
ANSI_RESET, (unsigned)(weave_switches - before));
}