feat: extend sched with currentThreadSetEnv and currentThreadUnsetEnv

This commit is contained in:
dualfroz committed 2026-09-07 13:17:42 +02:00
1 parent 7703e1a45d
commit 48b66a86f7
1 file changed
+173
+173
View File
@@ -837,6 +837,179 @@ const char* currentThreadGetEnv(const char* name)
return value + 1; return value + 1;
} }
} }
return nullptr;
}
bool currentThreadSetEnv(const char* name, const char* value)
{
Thread* self = &threads[currentIndex];
uint64 nameLen = strlen(name);
uint64 valueLen = strlen(value);
if (nameLen + 1 + valueLen + 1 > envEntryMax)
return false;
int slot = -1;
for (int i = 0; i < self->envCount; i++) {
if (envNameMatches(self->env[i], name)) {
slot = i;
break;
}
}
if (slot < 0) {
if (self->envCount >= envMaxVars)
return false;
slot = self->envCount++;
}
memcpy(self->env[slot], name, nameLen);
self->env[slot][nameLen] = '=';
memcpy(self->env[slot] + nameLen + 1, value, valueLen);
self->env[slot][nameLen + 1 + valueLen] = 0;
return true;
}
bool currentThreadUnsetEnv(const char* name)
{
Thread* self = &threads[currentIndex];
int slot = -1;
for (int i = 0; i < self->envCount; i++) {
if (envNameMatches(self->env[i], name)) {
slot = i;
break;
}
}
if (slot < 0)
return false;
int last = self->envCount - 1;
if (slot != last)
memmove(self->env[slot], self->env[slot + 1], (uint64)(last - slot) * envEntryMax);
self->envCount--;
return true;
}
int currentThreadEnvCount(void)
{
return threads[currentIndex].envCount;
}
const char* currentThreadEnvEntry(int index)
{
if (index < 0 || index >= threads[currentIndex].envCount)
return nullptr;
return threads[currentIndex].env[index];
}
bool threadStop(uint32 id)
{
if (id == 0 || id >= maxThreads)
return false;
ThreadState state = threads[id].state;
if (state == threadDead || state == threadUnused)
return false;
if (state == threadStopped) {
/* Idempotent, matching real kill(pid, SIGSTOP) sent to an already-
stopped process: the state transition already happened, so there
is no new event for the parent's SIGCHLD to report -- raising
again here would be a second notification for a single stop. */
return true;
}
threads[id].state = threadStopped;
kprintf("[sched] pid=%u '%s' stopped\n", id, threads[id].name);
/* Real job-control SIGCHLD-on-stop, the same asynchronous notification
threadExit/threadKill above already raise on death (Thread.parentId,
signal.h's sigRaiseInfo) -- a shell watching a background job's
parentId finds out the instant it stops, not only when it eventually
dies or exits. Only reached on the actual ...->Stopped transition
above, never on the already-Stopped no-op return just above.
siCodeChldStopped/id/sigStop: real Unix's WIFSTOPPED(status) +
WSTOPSIG(status) would report whichever signal actually caused the
stop -- this function is called from exactly two places (signal.c's
sigRaise sigStop branch, a real SIGSTOP; isr.c's sigTstp-default-
disposition branch, a real SIGTSTP) and does not itself know which
one, so si_status always reports sigStop here regardless. A known,
documented imprecision for the SIGTSTP path (which also has no live
ring-3 demo of its own yet -- see signal.h's file comment) -- not a
fabricated distinction either path never had. */
sigRaiseInfo(threads[id].parentId, sigChld, siCodeChldStopped, (int)id, sigStop);
return true;
}
bool threadContinue(uint32 id)
{
if (id == 0 || id >= maxThreads)
return false;
if (threads[id].state != threadStopped)
return false;
threads[id].state = threadRunnable;
kprintf("[sched] pid=%u '%s' continued\n", id, threads[id].name);
/* Same job-control SIGCHLD contract as threadStop above, mirrored for
SIGCONT: real job-control shells rely on this to notice a
backgrounded job resumed without polling its state. Only reached
past the "was it actually Stopped" guard above, so this never fires
for a SIGCONT sent to a target that was never stopped in the first
place, matching real Unix (nothing to report). siCodeChldContinued/
id/sigCont: real Unix's WIFCONTINUED(status), si_status unused for
this one (POSIX defines none for CLD_CONTINUED either) -- sigCont
here is honest, not a placeholder, unlike threadStop's own sigStop
above: threadContinue really is only ever reached via a genuine
SIGCONT (signal.c's sigRaise sigCont branch is its only caller with
a live path to a Stopped target). */
sigRaiseInfo(threads[id].parentId, sigChld, siCodeChldContinued, (int)id, sigCont);
return true;
}
bool threadWakeForSignal(uint32 id)
{
if (id == 0 || id >= maxThreads)
return false;
ThreadState state = threads[id].state;
if (state != threadSleeping && state != threadBlocked)
return false;
threads[id].state = threadRunnable;
kprintf("[sig] pid=%u '%s' woken early for a signal\n", id, threads[id].name);
return true;
}
uint32 threadCapacity(void)
{
return maxThreads;
}
/* Gives a dead/discarded user thread's whole elfSlotSize slot back to the
pmm. Verified, not just invoked blindly: a byte inside the slot's stack
region (always mapped for any user thread, whatever its image looks
like -- see loadImageByPath/spawnUser) is checked present BEFORE the
unmap (proving there was really something to reclaim, not a no-op on an
already-empty slot) and absent AFTER (proving the unmap actually took
the frame back), logged so the boot gate can hold the wiring itself
accountable rather than trusting that calling pagingUnmapUserRange was
enough. Kernel threads (userSlotBase 0) are silently skipped. */
static void reclaimUserSlot(uint32 id, const Thread* thread)
{
if (thread->userSlotBase == 0)
return;
uint64 slotBase = thread->userSlotBase;
uint64 stackProbe = slotBase + elfSlotStackOffset - 1;
bool wasMapped = pagingResolve(stackProbe) != 0;
pagingUnmapUserRange(slotBase, elfSlotSize);
bool nowFreed = pagingResolve(stackProbe) == 0;
kprintf("[sched] reap pid=%u freed user slot %p (was-mapped=%s now-freed=%s) -> %s\n",
id, (void*)slotBase, wasMapped ? "yes" : "no", nowFreed ? "yes" : "no",
(wasMapped && nowFreed) ? "user-mem-reclaimed-ok" : "user-mem-reclaimed-FAIL");
}
/* Every open fd a thread still holds when it dies represents a live
} }
void threadSleep(uint64 milliseconds) void threadSleep(uint64 milliseconds)