Files
frostbyte/include/sched.h
T

145 lines
6.5 KiB
C

#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