#ifndef FROSTBYTE_SCHED_H #define FROSTBYTE_SCHED_H #include "types.h" #include "vfs.h" #include "prioq.h" #include "isr.h" #include "signalnum.h" #include "mmap.h" /* Default scheduling weight (xv6/CFS-style "nice" niceWeight -- higher runs more often, not longer). threadSetPriority scales relative to this. */ #define schedDefaultWeight 1024u /* Returned by threadCreate/threadCreateUser when the thread table is full (see threadCapacity) -- never a valid thread id, since id 0 is always the permanent idle thread. Callers must check for this before using the result to index into anything thread-related. */ #define threadInvalidId 0xFFFFFFFFu /* Capacity of Thread.name, including the terminator. Large enough for every name this kernel currently produces (kernel thread literals, "/bin/..." paths passed to threadCreateUser by spawnUser/doSpawn) with room to spare; longer names are truncated, never overflowed. */ #define threadNameMax 32 /* Capacity of Thread.env below: envMaxVars is how many "NAME=VALUE" slots a process may hold at once, envEntryMax is the max size in bytes of a single slot INCLUDING both the '=' separator and the nul terminator (so "NAME=VALUE\0" must fit in envEntryMax bytes). Generous enough for anything this kernel's own shell/demos actually set (see user/envprint.c) with room to spare, the same "small fixed table, not a growable heap allocation" choice Thread.cwd already made for a single path. */ #define envMaxVars 8 #define envEntryMax 64 /* One bit per signal number in Thread.sigPending/sigBlocked, and the size of Thread.sigHandlers[] below -- see signal.h for the actual signal subsystem this supports. Defined here, not signal.h, purely to break the include cycle (signal.h includes sched.h for the Thread type). */ #define sigMaxSignals 32 /* Depth of the per-thread nested-handler context stack (Thread.sigSavedRegs/ sigSavedBlocked below) -- how many signal handlers may genuinely interrupt each other (handler A running, a second signal arrives and is not blocked by A's own sa_mask, so handler B runs nested inside A, then A resumes once B returns) before sigConsumePending (signal.c) refuses to deliver a new one and simply leaves it pending. A real kernel bounds this too (rlimit on the signal-frame stack it pushes into user memory); this one has no user-memory signal frame to overflow at all (see signal.h's file comment -- the saved context lives in the Thread struct itself), so the bound exists purely to keep that struct's size fixed and to fail a runaway signal-storm safely (further deliveries just wait) instead of unboundedly growing kernel memory per thread. 4 is generous headroom for anything this kernel's own handlers actually do (none nest more than one deep today -- see user/nesttarget.c) without being large enough to make Thread noticeably bigger than sigMaxSignals-worth of handlers already makes it. */ #define sigMaxNestDepth 4 typedef enum { threadUnused = 0, threadRunnable = 1, threadRunning = 2, threadSleeping = 3, threadDead = 4, threadBlocked = 5, /* Job-control stop (signal.h's SIGSTOP/SIGTSTP): excluded from schedule()'s ready-queue rebuild exactly like Sleeping/Blocked already are (only threadRunnable is ever inserted), so a stopped thread simply never gets picked until threadContinue moves it back to threadRunnable. Distinct from threadBlocked because it is NOT waiting on a wait-channel condition -- schedWakeChannel must never treat it as a match (see schedWakeChannel), only threadContinue (an explicit SIGCONT, not "the resource became available") ever moves a thread out of this state. */ threadStopped = 6 } ThreadState; typedef void (*ThreadEntry)(void); typedef struct Thread { uint64 rsp; uint64 stackBase; uint64 kernelStackTop; uint64 sleepUntil; /* Absolute pitTicks() deadline for a pending alarm(2)-style SIGALRM (0 == no alarm pending), set by threadSetAlarm and consulted once per timer tick by schedule() -- the exact same shape as sleepUntil above, checked in the exact same loop, except expiry raises a real signal (sigRaise, signal.h) against the owning thread instead of just flipping its own ThreadState back to Runnable. Deliberately a plain deadline, not a wait-channel/ThreadState change: unlike threadSleep, a thread with a pending alarm keeps running (or sleeping, or blocked on something unrelated) exactly as it already was right up until the deadline, exactly like real alarm()'s timer ticking down in the background regardless of what the process is doing. */ uint64 alarmAt; /* poll.h's sysPoll (syscall.c) uses the same restart-the-syscall park pattern sysRead/sysWrite/sysWait already use, but a poll call can be woken by any one of several unrelated fds, not one -- so instead of parking on a single fd's own wait channel (vfsBlockingChannel), sysPoll blocks every waiting thread on ONE shared channel (schedPollChannel below) and simply re-checks its whole fd list from scratch on every wake, spurious or not, exactly like the retry loop every OTHER restartable syscall already has. pollActive marks "this thread is mid a not-yet-satisfied sysPoll call" (so a retry entry can tell itself apart from a genuinely fresh call); pollDeadline is the absolute pitTicks() deadline schedule()'s own expiry scan forces a wake at if no fd ever becomes ready first (0 == wait forever, meaningful only while pollActive is true -- the same "0 reserved" convention alarmAt above already uses, but here 0 is a real, legal, requested state (a timeout of -1) rather than "nothing pending"). */ bool pollActive; uint64 pollDeadline; uint64 brk; uint64 vruntime; uint64 exitCode; uint32 weight; uint32 id; ThreadState state; const char* name; FileDescriptor fds[maxOpenFiles]; } Thread; void schedInit(void); uint32 threadCreate(const char* name, ThreadEntry entry); uint32 threadCreateUser(const char* name, uint64 entry, uint64 userStackTop); uint64 schedule(uint64 currentRsp); void schedStart(void); void threadExit(uint64 code); void threadSleep(uint64 milliseconds); void threadSetBrk(uint32 id, uint64 brk); uint64 currentThreadSbrk(int64 increment); uint32 threadCount(void); Thread* threadAt(uint32 index); Thread* currentThread(void); uint32 currentThreadId(void); #endif