From 268707b5719b4fe13fcef577306ec661932628a0 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 1 Sep 2026 21:39:05 +0200 Subject: [PATCH] feat: extend sched --- include/sched.h | 160 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 1 deletion(-) diff --git a/include/sched.h b/include/sched.h index a78f294..68a6abe 100644 --- a/include/sched.h +++ b/include/sched.h @@ -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;