feat: extend sched

This commit is contained in:
dualfroz committed 2026-09-01 21:39:05 +02:00
1 parent 30188c37d0
commit 268707b571
1 file changed
+159 -1
+159 -1
View File
@@ -120,8 +120,166 @@ typedef struct Thread {
uint64 exitCode;
uint32 weight;
uint32 id;
/* Id of whichever thread was actually running (currentIndex, sched.c)
at the moment this thread was created -- threadCreate/
threadCreateUser both set this unconditionally, so it is always
meaningful, not just for real userspace parent/child pairs: a
boot-time spawnUser (kmain.c) call happens before schedStart() ever
runs, so every process kmain.c spawns directly gets the permanent
idle thread (id 0) as its parentId, which is deliberately never a
valid sigRaise target (see signal.c's targetId==0 rejection) --
only a RUNTIME sysSpawn (syscall.c's doSpawn, called from inside a
live thread's own syscall) ever produces a parentId that is itself
a live, signalable process. Consulted by threadExit/threadKill
below to deliver a real SIGCHLD to whoever spawned this thread the
instant it actually dies, matching real wait()/SIGCHLD semantics --
NOT the same thing as "whoever later calls threadJoin/sysWait on
this id", which can be anyone that knows the pid, not just the
spawner (this kernel's honest fork()-less design already documents
that distinction, see doSpawn's own file comment in syscall.c). */
uint32 parentId;
/* Distinguishes THIS specific occupant of slot `id` from any other
thread that has occupied (or will ever occupy) the same numeric slot
before or after -- the exact same "handle number alone is not enough
once a slot is recycled" idea pipe.c's own Pipe.incarnation already
solves for pipe handles (see pipeIncarnation's own doc comment,
pipe.h). Assigned once, at creation time, from a single monotonic
counter (sched.c's nextThreadGeneration, never reused, mirroring
pipe.c's nextPipeIncarnation exactly) -- threadReap/threadDiscard's
memset resets this back to 0 when the slot is freed, and the next
threadCreate/threadCreateUser to land on that slot gets a brand new,
never-before-seen value, so 0 here always means "this exact slot is
currently unoccupied," never a live thread's own generation. Exists
purely so pidfd.h's pidfdOpen/pidfdTarget can tell "the same thread I
opened a handle on earlier" apart from "a different, unrelated thread
that has since been handed the same slot number" -- allocSlot's own
doc comment (sched.c) documents the real, previously-unguarded race
this closes for any caller that opts into pidfd.h instead of a raw
pid (kill/wait/sigSend/setpgid/nice below all still take a raw pid,
completely unchanged, exactly as that same comment explains why). */
uint32 generation;
/* Process group id (POSIX pgid): the scope real job control and real
kill(2)/sigSend's pid<0 convention actually target, not a single
thread (signalnum.h's sigInt/sigTstp arriving from keyboard.c's real
Ctrl-C/Ctrl-Z tracking are the live proof -- see keyboard.c's
keyboardHandleScancode and signal.c's sigRaiseGroup). Set once, at
creation time, by threadCreate/threadCreateUser's own initialPgid
helper (sched.c): a thread created while the idle thread (id 0) is
"current" -- i.e. one of kmain.c's boot-time spawnUser/threadCreate
calls, the only time that can ever be true, see parentId's own
comment just above -- becomes its own new group leader (pgid == its
own id), exactly like a real init process placing each service it
starts into its own job; a thread created by a genuinely running
parent (any runtime sysSpawn, syscall.c's doSpawn) instead INHERITS
that parent's pgid, matching real fork()'s own default (a child
stays in its parent's process group until something explicitly
calls setpgid). threadSetPgid (below) is the only thing that ever
changes it again after creation -- the sysSetpgid syscall's
userspace-facing half of real setpgid(2). */
uint32 pgid;
/* Real POSIX credential (uid(2)/setuid(2)): the identity vfs.c's
vfsPermitted consults, alongside a file's own owner uid and mode, to
decide owner-vs-other access at exactly the two enforcement choke
points fs.h's fsModeOwner and fsModeOther bits already gate
(resolveOpen's write check, loadImageByPath/jobctlSpawn's exec check) -- see
vfsPermitted's own doc comment for the full owner/other algorithm
(deliberately no uid-0-bypasses-everything superuser rule -- see that
same comment for why). Same creation-time default/copy split as pgid
immediately above (sched.c's new initialUid, mirroring initialPgid's
own "is the idle thread currently running" test): a boot-time thread
(kmain.c's spawnUser/threadCreate calls, idle currently "current")
starts as uid 0 ("root" in name only here, not in VFS privilege --
see fs.h's fsModeOwner* doc comment), exactly like a real init
process; a genuine runtime child (syscall.c's doSpawn) instead
INHERITS its parent's uid, matching real fork()'s own default before
any setuid ever runs. currentThreadSetUid (below) is the only thing
that ever changes it again after creation -- and, matching real
setuid(2)'s own privilege rule, only ever lets uid 0 become anyone;
any other uid may only "become" itself (a no-op), so dropping root is
real and permanent for the lifetime of this thread, exactly like a
real daemon that setuid()s away from root at startup and can never
regain it. */
uint32 uid;
/* Real POSIX group credential (getgid(2)/setgid(2)): the group identity
vfs.c's vfsPermitted consults, alongside a file's own group gid, as
the MIDDLE fallback between the owner and other checks fs.h's
fsModeGroup* bits gate. Same creation-time default/copy split as uid
immediately above (sched.c's new initialGid, mirroring initialUid's
own shape exactly): a boot-time thread starts at gid 0, a genuine
runtime child (syscall.c's doSpawn) INHERITS its parent's gid.
currentThreadSetGid (below) is the only thing that ever changes it
again -- unlike currentThreadSetUid, the privilege gate for THIS
call is the calling thread's own uid (currentThreadUid() == 0), not
its gid, matching real setgid(2): a privileged (root) PROCESS may
setgid to any group; an unprivileged one may only pass its own
current gid back in (a no-op), so dropping to a group is real and
permanent for the lifetime of a non-root thread, same shape as uid's
own one-way drop. */
uint32 gid;
/* Real per-process working directory (POSIX chdir(2)/getcwd(2)): always
a fully resolved absolute path (vfs.h's vfsChdir resolves a relative
argument against the CALLING thread's own cwd before ever storing
here, so this field itself never needs re-resolving against
anything). Set once, at creation time, by threadCreate/
threadCreateUser's own initialCwd helper (sched.c) -- a thread
created while the idle thread (id 0) is "current" (kmain.c's
boot-time spawns, see parentId's own comment above) starts at "/",
exactly like a real init process; a thread created by a genuinely
running parent (any runtime sysSpawn, syscall.c's doSpawn) instead
INHERITS a byte-for-byte COPY of that parent's cwd, matching real
fork()'s own copy-not-share semantics -- the two are independent
buffers from that point on, so a child's later chdir() can never
reach back and change its parent's, and vice versa. Before this
field existed, the only cwd concept anywhere in this kernel was a
single static local in shell.c shared by nothing else -- a spawned
user process calling open()/mkdir()/etc with a relative path had
never actually worked, since vfs.c's lookupNode/sfsLookup always
resolved relative to the filesystem root, never to any notion of
"current directory" (see vfs.c's vfsResolvePath, the only thing that
reads this field). */
char cwd[fsPathMax];
/* Real per-process environment variables (POSIX getenv(3)/setenv(3)):
up to envMaxVars "NAME=VALUE\0" strings, each at most envEntryMax-1
bytes, plus envCount tracking how many slots are actually in use.
Same creation-time default/copy split as Thread.cwd immediately
above (sched.c's new initialEnv, mirroring initialCwd's own shape):
a boot-time thread (idle is currently running) starts with an EMPTY
table, like a real init process before anything ever calls
setenv(); a genuine runtime child (syscall.c's doSpawn) instead
inherits a byte-for-byte COPY of its parent's table, so a child's
later setenv() can never reach back and change its parent's, and
vice versa -- the exact same independent-buffer guarantee cwd
already gives. */
char env[envMaxVars][envEntryMax];
int envCount;
ThreadState state;
const char* name;
/* Set once, permanently, the first time schedule() ever actually
switches execution to this thread. Distinguishes "freshly created,
never scheduled even once" (state threadRunnable, everRan false) from
"was running and got preempted, still Runnable" (everRan true) -- the
two look identical by state alone, but only the former is safe for
threadDiscard to tear down (see threadDiscard). */
bool everRan;
/* Fixed buffer, not a pointer: threadCreateUser/doSpawn (syscall.c) may
be called with a name that lives in a caller's stack-local buffer
(a path just copied in from userspace), which does not outlive the
call. thread->name must remain valid for the thread's entire life
(shell.c's `ps` listing and the exit log both read it long after
creation), so the bytes are always copied in, never aliased -- see
threadSetName/copyThreadName in sched.c. */
char name[threadNameMax];
const void* waitChannel;
/* Base virtual address of this thread's elfSlotSize-sized user-memory
slot (image + stack + brk-heap all live within
[userSlotBase, userSlotBase+elfSlotSize), see elf.h) -- 0 for kernel
threads (threadCreate), which own no user pages to give back.
Set from the entry point by threadCreateUser at spawn time and
updated by exec() (syscall.c doExec) when a thread's image moves to
a different slot. threadReap/threadDiscard pass this to
pagingUnmapUserRange so a dead or rolled-back process's physical
frames are actually reclaimed instead of leaking forever. */
uint64 userSlotBase;
PrioQNode schedNode;
FileDescriptor fds[maxOpenFiles];
} Thread;