feat: grow sched
This commit is contained in:
1 parent
785e147d44
commit
103abbb35f
1 file changed
+208
+208
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user