feat: grow sched

This commit is contained in:
dualfroz committed 2026-09-02 11:18:40 +02:00
1 parent 785e147d44
commit 103abbb35f
1 file changed
+208
+208
View File
@@ -533,6 +533,214 @@ void threadSleep(uint64 milliseconds);
void threadSetBrk(uint32 id, uint64 brk);
uint64 currentThreadSbrk(int64 increment);
/* Real alarm(2): schedules a real SIGALRM (signalnum.h's sigAlrm) against
the CALLING thread only, `seconds` real seconds from now (converted to
pitTicks() ticks via pitFrequency(), the same conversion threadSleep
already uses for milliseconds) -- never another thread, exactly like
POSIX alarm() has no target-pid argument at all. `seconds` == 0 cancels
any alarm already pending for this thread instead of scheduling a new
one, matching real alarm(0). Returns the number of whole seconds that
remained on a PREVIOUSLY pending alarm for this thread (rounded up, so a
deadline even one tick in the future still reports at least 1, exactly
like real alarm() never reports "0 remaining" for one that has not
actually fired yet), or 0 if none was pending -- the caller's own
previous alarm is silently replaced (or cleared) either way, matching
real alarm()'s "at most one pending alarm per process" contract. Actual
delivery happens later, from schedule() (sched.c), once pitTicks()
reaches the deadline this call records. */
uint64 threadSetAlarm(uint64 seconds);
/* Weighted-fair scheduling (CFS-lite): every thread starts at
schedDefaultWeight. A thread with weight W accrues virtual runtime
schedDefaultWeight/W times as fast as a default-weight thread each time
it runs, so it is picked from the ready queue proportionally more often
-- not given a longer timeslice, since this scheduler's granularity is
already one timer tick. Returns false (no-op) for the same targets
threadSetPgid already refuses -- the idle thread (id 0), an out-of-range
id, or a slot that is not currently a live thread (Dead or Unused) --
plus a zero weight, which used to be silently ignored (kmain.c-only
caller never triggered it) before this became reachable from userspace
through sysNice (syscall.c). Was a kernel-internal helper only used
once, at boot, for workerA/workerB's own priority demo (kmain.c) --
still is, but ALSO now the mechanism sysNice's syscall.c handler calls,
so its own validation had to catch up to threadSetPgid's, not stay at
the older, weaker "just check threadTotal" bound that let a userspace
nice() call quietly reweight thread 0 (idle) or a slot no thread has
ever occupied. */
bool threadSetPriority(uint32 id, uint32 weight);
/* Returns thread `id`'s current scheduling weight, or 0 (never a real
weight -- threadSetPriority above always rejects weight 0, and every
thread starts at schedDefaultWeight) for an out-of-range id or a slot
that has never been a live thread (Unused) -- same "0 doubles as the
error return, since it is never a valid one" shape threadPgid below
has with threadInvalidId. A Dead-but-not-yet-reaped thread still
reports its last real weight, exactly like threadPgid still reports
its last real pgid for the same state -- see that function's own
comment for why. */
uint32 threadPriority(uint32 id);
uint64 threadVruntime(uint32 id);
/* Returns thread `id`'s process group id (Thread.pgid above), or
threadInvalidId for an out-of-range id or a slot that is not currently a
live thread -- the same "invalid id sentinel doubles as an error return"
convention threadInvalidId already has for allocation failure, safe here
because a real pgid is always some other live thread's own id (never
0xFFFFFFFF, since ids never reach that high, see threadCapacity). */
uint32 threadPgid(uint32 id);
/* Real setpgid(2)'s underlying mechanism: unconditionally sets thread
`id`'s pgid to `pgid`, whatever value the caller resolved "0 means self"/
"0 means use pid itself as the new group id" down to (that resolution is
the SYSCALL layer's job, syscall.c's sysSetpgid -- mirroring how
threadSetBrk/threadSetPriority/threadSetAlarm above already take a fully
resolved value, not a raw syscall argument). Deliberately does not
validate that `pgid` names any other real thread at all: this kernel has
no session concept to check membership against (real setpgid(2) would
reject joining a group outside the caller's own session), so -- like
several other omissions this codebase documents honestly rather than
fakes -- any uint32 is accepted, exactly as if every process were in one
giant session. Returns false, changing nothing, for id 0 (idle -- it has
no job-control identity to reassign) or an out-of-range/Dead/Unused id. */
bool threadSetPgid(uint32 id, uint32 pgid);
/* Real kill(-pgid, SIGKILL)-shaped bulk termination: calls threadKill on
every currently live thread (Runnable/Sleeping/Blocked/Stopped, per
threadKill's own accepted set) whose pgid equals `pgid`, one at a time,
through the ordinary single-target threadKill above -- so its own
per-target SIGCHLD-to-parent notification and wait-channel wakes already
apply per victim exactly as they would for a plain, non-group kill,
rather than this function duplicating any of that. A pgid whose only
member is the CALLER's own currently-Running thread naturally kills
nobody (threadKill excludes Running, i.e. self, by construction, the
same "self-kill via plain kill(2) is not really different" reasoning
userKill's own doc comment already gives) without needing a special
case here. Returns false, killing nobody, if no currently live thread
has this pgid at all -- the real ESRCH case for kill(-pgid, ...) with no
matching group. */
bool threadKillGroup(uint32 pgid);
/* Real getcwd(2)/chdir(2)'s underlying per-thread state (Thread.cwd above):
always the CALLING thread's own directory, never another pid's -- unlike
threadSetPgid, there is no `id` parameter here at all, since POSIX's
chdir() has no cross-process form the way setpgid(2) does for a child
still between fork() and exec(). currentThreadSetCwd does no validation
of its own (mirrors threadSetPgid's own "that is the syscall layer's
job" split) -- vfs.c's vfsChdir is what actually confirms `path` names a
real, already-resolved-to-absolute directory before ever calling this;
a `path` longer than fsPathMax-1 is rejected here (false, unchanged)
since it could never round-trip back out through currentThreadCwd(). */
const char* currentThreadCwd(void);
bool currentThreadSetCwd(const char* path);
/* Real getuid(2)/setuid(2)'s underlying per-thread state (Thread.uid
above): always the CALLING thread's own identity, mirroring
currentThreadCwd's own "no id parameter, no cross-process form" shape
exactly (POSIX setuid() has no cross-process form either -- a process can
only ever change its OWN credential). currentThreadSetUid enforces the
real privilege rule itself (this kernel's syscall layer does no
additional validation of its own, unlike currentThreadSetCwd's split):
uid 0 ("root") may become anyone; any other uid may only pass its own
current value back in (a no-op that still reports success, matching real
setuid(getuid())), and any other target is rejected, state unchanged.
There is deliberately no way back to uid 0 once dropped, short of
already being uid 0 at the time of the call -- the same one-way
privilege-drop shape a real setuid daemon relies on. */
uint32 currentThreadUid(void);
bool currentThreadSetUid(uint32 newUid);
/* Real getgid(2)/setgid(2)'s underlying per-thread state (Thread.gid above)
-- same "no id parameter, no cross-process form" shape as currentThreadUid
immediately above. currentThreadSetGid's privilege gate is the CALLING
thread's own currentThreadUid() (== 0), not its gid -- matching real
setgid(2), whose privilege check is CAP_SETGID (effectively "is this
process root"), never based on the gid being changed itself. A
non-privileged thread may only pass its own current gid back in (a
no-op success, matching real setgid(getgid())); any other target is
rejected, state unchanged. */
uint32 currentThreadGid(void);
bool currentThreadSetGid(uint32 newGid);
/* Real getenv(3)/setenv(3)'s underlying per-thread state (Thread.env
above): always the CALLING thread's own table, mirroring
currentThreadCwd/currentThreadSetCwd's own "no id parameter, no
cross-process form" shape exactly (POSIX setenv() has no cross-process
form either). currentThreadGetEnv returns a pointer straight into the
matching slot's own VALUE half (never a copy) or nullptr if `name` has
no entry -- that pointer stays valid only until the next
currentThreadSetEnv call on the SAME name, which may overwrite the slot
in place. currentThreadSetEnv overwrites an existing entry with a
matching `name` in place, or appends a new one if a slot is still free
under envMaxVars; returns false, table unchanged, if `name`+'='+`value`
would not fit in envEntryMax bytes, or if `name` is new and every slot
is already taken. */
const char* currentThreadGetEnv(const char* name);
bool currentThreadSetEnv(const char* name, const char* value);
/* Real unsetenv(3): removes the calling thread's own entry named `name`,
if any, compacting the table (memmove of every later slot down by one)
so envCount and currentThreadEnvEntry's own index space never develop a
hole -- unlike currentThreadSetEnv's in-place overwrite, this changes
WHICH index every later entry lives at. Returns true if an entry was
actually removed, false (table unchanged) if `name` had no entry --
matching real unsetenv(3), which is a no-op success either way, but the
bool lets a caller (shell.c's `unset` builtin) tell the difference if it
ever wants to. */
bool currentThreadUnsetEnv(const char* name);
/* Iterates the calling thread's own environment table for listing
(shell.c's `env` builtin): currentThreadEnvCount() is how many of the
envMaxVars slots are actually in use; currentThreadEnvEntry(index)
returns the raw "NAME=VALUE" string stored at that slot, or nullptr for
an out-of-range index. */
int currentThreadEnvCount(void);
const char* currentThreadEnvEntry(int index);
/* Copies up to threadNameMax-1 bytes of `name` into thread `id`'s own name
buffer (truncating, always nul-terminated). Used by exec() (syscall.c) so
a process's visible identity (shell `ps`, the exit log) reflects the new
image after it replaces the old one -- the same way a real process's
name changes across execve(). Invalid id is a silent no-op. */
void threadSetName(uint32 id, const char* name);
/* Base of thread `id`'s user-memory slot (see Thread.userSlotBase above),
or 0 for an out-of-range id or a kernel thread that owns no user slot.
Used by exec() (syscall.c) to find the OLD slot to reclaim before it
overwrites its own image with a new one. */
uint64 threadUserSlotBase(uint32 id);
/* Records which elfSlotSize-sized slot thread `id` currently occupies.
Only exec() (syscall.c) needs this directly -- threadCreateUser sets it
itself from the entry point it is given. Invalid id is a silent no-op. */
void threadSetUserSlotBase(uint32 id, uint64 base);
/* Wait-queue primitives ("sleep on a channel", xv6-style). A channel is any
stable address the caller controls (e.g. the address of the resource being
waited on) - it is never dereferenced, only compared for identity.
schedBlockCurrent marks the calling thread as blocked on a channel; it does
NOT itself give up the CPU (this scheduler is timer-preemptive, not
cooperative - callers park with halt() the same way threadSleep callers
already do, see kmain.c). schedWakeChannel moves every thread blocked on a
matching channel back to runnable; it is always safe to call even when no
thread is waiting (no-op) and must be called by whichever side of a
producer/consumer relationship changes the condition the other side is
waiting on. */
void schedBlockCurrent(const void* channel);
void schedWakeChannel(const void* channel);
/* The one wait channel every not-yet-satisfied sysPoll call blocks on (see
Thread.pollActive's own comment above) -- any code path that can make a
previously-not-ready fd become ready (pipe.c's pipeWrite/pipeRead
freeing space, pipeCloseRead/pipeCloseWrite breaking a pipe,
keyboard.c's keyboardHandleScancode enqueueing a keystroke) calls
schedWakeChannel(schedPollChannel()) in addition to whatever
fd-specific channel it already wakes, so every blocked poller gets a
chance to re-check its own fd list. Always returns the same address for
the life of the kernel -- not tied to any particular fd or pipe, unlike
pipeChannel/keyboardReaderChannel. */
const void* schedPollChannel(void);
uint32 threadCount(void);
Thread* threadAt(uint32 index);
Thread* currentThread(void);