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
This commit is contained in:
Claude committed 2026-08-25 08:16:37 +00:00
1 parent ecdc5be435
commit a14a7c79b0
10 files changed
+570 -58

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
/*
* SUB-OS kernel context switch (AArch64)
* --------------------------------------
* void sub_ctx_switch(void** save_sp, void* load_sp)
* x0 = save_sp, x1 = load_sp
*
* Saves the AAPCS64 callee-saved registers (x19-x28) plus the frame pointer
* (x29) and link register (x30) of the outgoing thread, records its stack
* pointer through *save_sp, then adopts the incoming thread's stack and
* restores the mirror image. Returns into whoever is switched in.
*
* NOTE: implemented for parity with x86_64 and to keep the shared scheduler
* building on this target; not yet exercised on hardware/QEMU for aarch64.
*/
.text
.globl sub_ctx_switch
.globl sub_thread_trampoline
sub_ctx_switch:
sub sp, sp, #(12 * 8)
stp x19, x20, [sp, #0]
stp x21, x22, [sp, #16]
stp x23, x24, [sp, #32]
stp x25, x26, [sp, #48]
stp x27, x28, [sp, #64]
stp x29, x30, [sp, #80] /* fp, lr */
mov x2, sp
str x2, [x0] /* *save_sp = outgoing SP */
mov sp, x1 /* adopt incoming SP */
ldp x19, x20, [sp, #0]
ldp x21, x22, [sp, #16]
ldp x23, x24, [sp, #32]
ldp x25, x26, [sp, #48]
ldp x27, x28, [sp, #64]
ldp x29, x30, [sp, #80]
add sp, sp, #(12 * 8)
ret
/* Fresh-thread trampoline: x19 = entry, x20 = argument (forged by task_create). */
sub_thread_trampoline:
mov x0, x20 /* arg */
blr x19 /* entry(arg) */
mov x0, #0
bl task_exit
1: wfi
b 1b
+34
View File
@@ -0,0 +1,34 @@
/*
* SUB-OS kernel context switch (ARMv8i / AArch32)
* -----------------------------------------------
* void sub_ctx_switch(void** save_sp, void* load_sp)
* r0 = save_sp, r1 = load_sp
*
* Saves the AAPCS callee-saved registers (r4-r11) plus the link register of
* the outgoing thread, records its stack pointer through *save_sp, then adopts
* the incoming thread's stack and restores the mirror image.
*
* NOTE: implemented for parity with x86_64 and to keep the shared scheduler
* building on this target; not yet exercised on hardware/QEMU for armv8i.
*/
.text
.syntax unified
.arm
.globl sub_ctx_switch
.globl sub_thread_trampoline
sub_ctx_switch:
push {r4-r11, lr}
str sp, [r0] @ *save_sp = outgoing SP
mov sp, r1 @ adopt incoming SP
pop {r4-r11, lr}
bx lr
/* Fresh-thread trampoline: r4 = entry, r5 = argument (forged by task_create). */
sub_thread_trampoline:
mov r0, r5 @ arg
blx r4 @ entry(arg)
mov r0, #0
bl task_exit
1: b 1b
+55
View File
@@ -0,0 +1,55 @@
[BITS 64]
default rel
; SUB-OS kernel context switch (x86_64)
; -------------------------------------
; sub_ctx_switch(void** save_sp /* rdi */, void* load_sp /* rsi */)
;
; Saves the SysV callee-saved registers plus RFLAGS of the outgoing thread onto
; its own stack, stashes the resulting stack pointer through *save_sp, then
; loads the incoming thread's stack pointer and unwinds the mirror image. From
; C this looks like an ordinary call that "returns" only when this thread is
; later switched back in. Caller-saved registers are the C caller's problem, as
; usual, so nothing else needs preserving here.
global sub_ctx_switch
global sub_thread_trampoline
extern task_exit
section .text
sub_ctx_switch:
push rbp
push rbx
push r12
push r13
push r14
push r15
pushfq
mov [rdi], rsp ; *save_sp = outgoing stack pointer
mov rsp, rsi ; adopt the incoming stack
popfq
pop r15
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
; First-run trampoline. task_create() forges an initial frame so that the very
; first switch into a fresh thread lands here with r12 = entry, r13 = argument.
; We call the entry function, and if it ever returns we retire the thread.
sub_thread_trampoline:
mov rdi, r13 ; arg
call r12 ; entry(arg)
xor rdi, rdi ; void entry -> exit code 0
call task_exit
.hang:
hlt
jmp .hang
section .note.GNU-stack noalloc noexec nowrite progbits