diff --git a/src/sched.c b/src/sched.c index 530e189..09ed9f6 100644 --- a/src/sched.c +++ b/src/sched.c @@ -575,31 +575,77 @@ uint32 threadCreateUser(const char* name, uint64 entry, uint64 userStackTop) return slot; } +/* CFS-lite selection: every reschedule rebuilds a min-vruntime priority + queue (prioq.h, backed by the intrusive rbt) from every currently + Runnable thread and picks the minimum. The queue is deliberately + ephemeral (built fresh each call, discarded after one pop) rather than a + run-queue whose membership persists across calls -- a thread can become + Runnable from code paths other than schedule() itself (schedWakeChannel, + called from pipe.c on the producer/consumer side of a wait-queue), and + rebuilding from the authoritative `state` field every time means there is + never a second copy of "who is runnable" to fall out of sync. */ uint64 schedule(uint64 currentRsp) { if (!schedulerActive) return currentRsp; - threads[currentIndex].rsp = currentRsp; - if (threads[currentIndex].state == threadRunning) - threads[currentIndex].state = threadRunnable; + Thread* current = &threads[currentIndex]; + current->rsp = currentRsp; + if (current->state == threadRunning) { + current->vruntime += schedTickCharge * schedDefaultWeight / current->weight; + current->state = threadRunnable; + } uint64 now = pitTicks(); for (uint32 i = 0; i < threadTotal; i++) if (threads[i].state == threadSleeping && now >= threads[i].sleepUntil) threads[i].state = threadRunnable; - uint32 next = 0; - for (uint32 step = 1; step <= threadTotal; step++) { - uint32 candidate = (currentIndex + step) % threadTotal; - if (threads[candidate].state == threadRunnable) { - next = candidate; - break; + /* sysPoll's own timeout, forced the exact same way sleepUntil's expiry + is above: a thread parked on schedPollChannel because none of its + requested fds were ready yet still needs to wake up on its own even + if NONE of them ever becomes ready (pollDeadline == 0 means "wait + forever", i.e. a real timeout of -1, and is deliberately never + forced here). The retried sysPoll call (syscall.c) re-checks every + fd from scratch on wake regardless of why it woke, so forcing + Runnable here without clearing waitChannel first (same as the + sleepUntil scan just above) is enough -- there is no separate + "why did I wake" state to reconcile. */ + for (uint32 i = 0; i < threadTotal; i++) + if (threads[i].state == threadBlocked && threads[i].pollActive && + threads[i].pollDeadline != 0 && now >= threads[i].pollDeadline) + threads[i].state = threadRunnable; + + /* Real alarm(2) delivery: a deadline set by threadSetAlarm fires here, + once per timer tick, regardless of what state its owning thread is + currently in (Running, Sleeping, Blocked -- a real alarm keeps + ticking no matter what the process is doing). Cleared BEFORE + raising, not after, so one expiry fires sigAlrm exactly once, not + once per tick until something else clears it; sigRaiseInfo (signal.h) + already refuses a Dead/Unused target harmlessly, so a thread that + exited with an alarm still pending needs no special case here. + siCodeKernel/0/0: a real SIGALRM has no sending process and no exit + status of its own to report (real Unix's own SIGALRM si_code is + SI_KERNEL/SI_TIMER for exactly this reason -- it is the kernel's + timer, not another process, that raised it). */ + for (uint32 i = 0; i < threadTotal; i++) + if (threads[i].alarmAt != 0 && now >= threads[i].alarmAt) { + threads[i].alarmAt = 0; + sigRaiseInfo(threads[i].id, sigAlrm, siCodeKernel, 0, 0); } - } + + PrioQueue ready; + prioqInit(&ready); + for (uint32 i = 0; i < threadTotal; i++) + if (threads[i].state == threadRunnable) + prioqInsert(&ready, &threads[i].schedNode, threads[i].vruntime, threads[i].id); + + PrioQNode* winner = prioqPopMin(&ready); + uint32 next = winner ? winner->id : currentIndex; currentIndex = next; threads[next].state = threadRunning; + threads[next].everRan = true; tssSetRsp0(threads[next].kernelStackTop); return threads[next].rsp; } @@ -615,6 +661,7 @@ void schedStart(void) void threadExit(uint64 code) { Thread* thread = &threads[currentIndex]; + thread->exitCode = code; thread->state = threadDead; kprintf("[user] pid=%u '%s' exited with code %lu\n", thread->id, thread->name, code); }