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);
|
void threadSetBrk(uint32 id, uint64 brk);
|
||||||
uint64 currentThreadSbrk(int64 increment);
|
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);
|
uint32 threadCount(void);
|
||||||
Thread* threadAt(uint32 index);
|
Thread* threadAt(uint32 index);
|
||||||
Thread* currentThread(void);
|
Thread* currentThread(void);
|
||||||
|
|||||||
Reference in new issue
Block a user