feat: extend sched with reclaimOpenFds and threadReap
This commit is contained in:
1 parent
48b66a86f7
commit
3376f44f5e
1 file changed
+203
-1
+203
-1
@@ -1010,6 +1010,119 @@ static void reclaimUserSlot(uint32 id, const Thread* thread)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Every open fd a thread still holds when it dies represents a live
|
/* Every open fd a thread still holds when it dies represents a live
|
||||||
|
reference into some other subsystem's own refcounted table -- fd 0's
|
||||||
|
console reader queue (keyboard.h, allocated unconditionally by vfsFdInit
|
||||||
|
regardless of whether the thread ever reads stdin, see vfs.c) and any
|
||||||
|
fdPipeRead/fdPipeWrite fd (pipe.h's readers/writers counts on the
|
||||||
|
underlying handle, from vfsPipe/vfsPipeAttach/a named fifo's vfsOpen).
|
||||||
|
vfsClose already decrements both correctly, but only for a LIVE thread
|
||||||
|
closing its OWN fd through currentThread() -- a thread that dies
|
||||||
|
(threadKill, or simply returns/threadExit) while still holding either
|
||||||
|
open skips vfsClose entirely, so without this the memset below would
|
||||||
|
just drop the handle on the floor.
|
||||||
|
For the console reader this was caught live (FALA 13): the reader table
|
||||||
|
is a small fixed pool (maxConsoleReaders), and schedTest.c alone creates
|
||||||
|
and tears down dozens of throwaway threads while proving the sched table
|
||||||
|
itself reuses freed slots (threadCreateExhaustsThenThreadDiscardFrees...),
|
||||||
|
which permanently exhausted the ENTIRELY SEPARATE console reader pool
|
||||||
|
before boot ever reached kmain.c's real spawnUser calls -- every
|
||||||
|
boot-time process's fd 0 silently went non-blocking (keyboardOpenReader
|
||||||
|
returning consoleReaderInvalid), so consolereader1.c/consolereader2.c's
|
||||||
|
userRead(0, ...) returned -1 immediately instead of blocking for the
|
||||||
|
synthetic keystroke.
|
||||||
|
The pipe/fifo case is the same shape of leak, just against pipe.c's own
|
||||||
|
readers/writers counters instead of a fixed pool: the OTHER end of a
|
||||||
|
shared pipe (a second, independent process, e.g. fifomaster.c's
|
||||||
|
children) would never see the true EOF/broken-pipe pipeRead/pipeWrite
|
||||||
|
report once every attached end on this side is gone, and would instead
|
||||||
|
retry "try again" forever -- exactly the resource-exhaustion shape
|
||||||
|
bug#2 above had, just not yet observed breaking anything live because
|
||||||
|
nothing today loses a pipe/fifo fd without an orderly close() first (see
|
||||||
|
schedTest.c's reclaimOpenFds tests, which prove the leak by fault
|
||||||
|
injection rather than waiting for a live symptom). */
|
||||||
|
static void reclaimOpenFds(const Thread* thread)
|
||||||
|
{
|
||||||
|
for (uint32 i = 0; i < maxOpenFiles; i++) {
|
||||||
|
const FileDescriptor* fd = &thread->fds[i];
|
||||||
|
if (fd->kind == fdConsole && fd->pipeHandle >= 0)
|
||||||
|
keyboardCloseReader(fd->pipeHandle);
|
||||||
|
else if (fd->kind == fdPipeRead)
|
||||||
|
pipeCloseRead(fd->pipeHandle);
|
||||||
|
else if (fd->kind == fdPipeWrite)
|
||||||
|
pipeCloseWrite(fd->pipeHandle);
|
||||||
|
else if (fd->kind == fdPidfd)
|
||||||
|
pidfdClose(fd->pipeHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool threadReap(uint32 id)
|
||||||
|
{
|
||||||
|
if (id == 0 || id >= maxThreads)
|
||||||
|
return false;
|
||||||
|
if (threads[id].state != threadDead)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
reclaimUserSlot(id, &threads[id]);
|
||||||
|
reclaimOpenFds(&threads[id]);
|
||||||
|
if (threads[id].stackBase)
|
||||||
|
kfree((void*)threads[id].stackBase);
|
||||||
|
memset(&threads[id], 0, sizeof(Thread));
|
||||||
|
/* memset leaves state at 0, which is threadUnused -- exactly the value
|
||||||
|
allocSlot() scans for when looking for a slot to reuse. */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool threadDiscard(uint32 id)
|
||||||
|
{
|
||||||
|
if (id == 0 || id >= maxThreads)
|
||||||
|
return false;
|
||||||
|
if (threads[id].state != threadRunnable || threads[id].everRan)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (threads[id].userSlotBase != 0)
|
||||||
|
pagingUnmapUserRange(threads[id].userSlotBase, elfSlotSize);
|
||||||
|
reclaimOpenFds(&threads[id]);
|
||||||
|
if (threads[id].stackBase)
|
||||||
|
kfree((void*)threads[id].stackBase);
|
||||||
|
memset(&threads[id], 0, sizeof(Thread));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 threadJoin(uint32 id)
|
||||||
|
{
|
||||||
|
if (id == 0 || id >= maxThreads)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
ThreadState state = threads[id].state;
|
||||||
|
if (state == threadUnused)
|
||||||
|
return -1;
|
||||||
|
if (state == threadDead)
|
||||||
|
break;
|
||||||
|
schedBlockCurrent(&threads[id]);
|
||||||
|
halt();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 code = threads[id].exitCode;
|
||||||
|
threadReap(id);
|
||||||
|
return (int64)code;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitScanResult threadJoinAnyChild(uint32 parentId, uint32* outId, int64* outCode)
|
||||||
|
{
|
||||||
|
bool hasChild = false;
|
||||||
|
for (uint32 i = 1; i < threadTotal; i++) {
|
||||||
|
if (threads[i].state == threadUnused || threads[i].parentId != parentId)
|
||||||
|
continue;
|
||||||
|
hasChild = true;
|
||||||
|
if (threads[i].state == threadDead) {
|
||||||
|
*outId = i;
|
||||||
|
*outCode = (int64)threads[i].exitCode;
|
||||||
|
threadReap(i);
|
||||||
|
return waitScanFoundDead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasChild ? waitScanNoneDead : waitScanNoChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
void threadSleep(uint64 milliseconds)
|
void threadSleep(uint64 milliseconds)
|
||||||
@@ -1021,18 +1134,107 @@ void threadSleep(uint64 milliseconds)
|
|||||||
threads[currentIndex].state = threadSleeping;
|
threads[currentIndex].state = threadSleeping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64 threadSetAlarm(uint64 seconds)
|
||||||
|
{
|
||||||
|
Thread* self = &threads[currentIndex];
|
||||||
|
uint64 now = pitTicks();
|
||||||
|
|
||||||
|
uint64 previousRemaining = 0;
|
||||||
|
if (self->alarmAt != 0 && self->alarmAt > now) {
|
||||||
|
uint64 remainingTicks = self->alarmAt - now;
|
||||||
|
uint32 freq = pitFrequency();
|
||||||
|
/* Ceiling division: a deadline anywhere inside the current second
|
||||||
|
still counts as "1 second remaining", never "0" -- matching real
|
||||||
|
alarm()'s own contract that a not-yet-fired alarm never reports
|
||||||
|
0 remaining (0 is reserved for "none pending" everywhere in this
|
||||||
|
API, see the header comment). */
|
||||||
|
previousRemaining = (remainingTicks + freq - 1) / freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds == 0)
|
||||||
|
self->alarmAt = 0;
|
||||||
|
else
|
||||||
|
self->alarmAt = now + seconds * (uint64)pitFrequency();
|
||||||
|
|
||||||
|
return previousRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
void schedBlockCurrent(const void* channel)
|
||||||
|
{
|
||||||
|
threads[currentIndex].waitChannel = channel;
|
||||||
|
threads[currentIndex].state = threadBlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* schedPollChannel(void)
|
||||||
|
{
|
||||||
|
static const int pollChannelToken;
|
||||||
|
return (const void*)&pollChannelToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
void schedWakeChannel(const void* channel)
|
||||||
|
{
|
||||||
|
for (uint32 i = 0; i < threadTotal; i++) {
|
||||||
|
if (threads[i].state == threadBlocked && threads[i].waitChannel == channel) {
|
||||||
|
threads[i].waitChannel = nullptr;
|
||||||
|
threads[i].state = threadRunnable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void threadSetBrk(uint32 id, uint64 brk)
|
void threadSetBrk(uint32 id, uint64 brk)
|
||||||
{
|
{
|
||||||
|
if (id >= maxThreads)
|
||||||
|
return;
|
||||||
threads[id].brk = brk;
|
threads[id].brk = brk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64 threadUserSlotBase(uint32 id)
|
||||||
|
{
|
||||||
|
if (id >= maxThreads)
|
||||||
|
return 0;
|
||||||
|
return threads[id].userSlotBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
void threadSetUserSlotBase(uint32 id, uint64 base)
|
||||||
|
{
|
||||||
|
if (id >= maxThreads)
|
||||||
|
return;
|
||||||
|
threads[id].userSlotBase = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool threadSetPriority(uint32 id, uint32 weight)
|
||||||
|
{
|
||||||
|
if (id == 0 || id >= maxThreads || weight == 0)
|
||||||
|
return false;
|
||||||
|
ThreadState state = threads[id].state;
|
||||||
|
if (state == threadDead || state == threadUnused)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
threads[id].weight = weight;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 threadPriority(uint32 id)
|
||||||
|
{
|
||||||
|
if (id >= maxThreads || threads[id].state == threadUnused)
|
||||||
|
return 0;
|
||||||
|
return threads[id].weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 threadVruntime(uint32 id)
|
||||||
|
{
|
||||||
|
if (id >= threadTotal)
|
||||||
|
return 0;
|
||||||
|
return threads[id].vruntime;
|
||||||
|
}
|
||||||
|
|
||||||
uint64 currentThreadSbrk(int64 increment)
|
uint64 currentThreadSbrk(int64 increment)
|
||||||
{
|
{
|
||||||
Thread* thread = &threads[currentIndex];
|
Thread* thread = &threads[currentIndex];
|
||||||
uint64 previous = thread->brk;
|
uint64 previous = thread->brk;
|
||||||
|
|
||||||
if (increment > 0)
|
if (increment > 0)
|
||||||
pagingMapUserRange(previous, (uint64)increment);
|
pagingMapUserRangeFlags(previous, (uint64)increment, pageNoExecute);
|
||||||
|
|
||||||
thread->brk = previous + (uint64)increment;
|
thread->brk = previous + (uint64)increment;
|
||||||
return previous;
|
return previous;
|
||||||
|
|||||||
Reference in new issue
Block a user