feat: extend kmain with schedPriorityVerifierThread
This commit is contained in:
1 parent
0cd28abb5d
commit
661ff83253
1 file changed
+185
-10
+185
-10
@@ -15,9 +15,16 @@
|
||||
#include "initrd.h"
|
||||
#include "vfs.h"
|
||||
#include "elf.h"
|
||||
#include "ktest.h"
|
||||
#include "pipe.h"
|
||||
#include "klib.h"
|
||||
#include "signal.h"
|
||||
#include "ports.h"
|
||||
#include "ata.h"
|
||||
#include "sfs.h"
|
||||
|
||||
#define multibootBootMagic 0x36D76289
|
||||
#define heapSize (8 * 1024 * 1024)
|
||||
#define heapSize (48 * 1024 * 1024)
|
||||
#define timerFrequency 100
|
||||
#define userStackSize 0x4000
|
||||
|
||||
@@ -37,14 +44,20 @@ static void shellThread(void)
|
||||
shellRun();
|
||||
}
|
||||
|
||||
/* Exposed at file scope (rather than local to workerA/workerB) so
|
||||
schedPriorityVerifierThread can read them back and turn "the scheduler
|
||||
favors higher weight" into an automated, gate-checked assertion instead
|
||||
of something a human has to eyeball in the boot log. */
|
||||
static volatile uint64 workerAIterations;
|
||||
static volatile uint64 workerBIterations;
|
||||
|
||||
static void workerA(void)
|
||||
{
|
||||
uint64 iterations = 0;
|
||||
uint64 nextLog = 50;
|
||||
for (;;) {
|
||||
iterations++;
|
||||
workerAIterations++;
|
||||
if (pitTicks() >= nextLog) {
|
||||
kprintf("[workerA] tick=%lu iterations=%lu\n", pitTicks(), iterations);
|
||||
kprintf("[workerA] tick=%lu iterations=%lu\n", pitTicks(), workerAIterations);
|
||||
nextLog += 100;
|
||||
}
|
||||
}
|
||||
@@ -52,17 +65,179 @@ static void workerA(void)
|
||||
|
||||
static void workerB(void)
|
||||
{
|
||||
uint64 iterations = 0;
|
||||
uint64 nextLog = 90;
|
||||
for (;;) {
|
||||
iterations++;
|
||||
workerBIterations++;
|
||||
if (pitTicks() >= nextLog) {
|
||||
kprintf("[workerB] tick=%lu iterations=%lu\n", pitTicks(), iterations);
|
||||
kprintf("[workerB] tick=%lu iterations=%lu\n", pitTicks(), workerBIterations);
|
||||
nextLog += 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define schedPriorityCheckDelayMs 15000
|
||||
#define schedPriorityWeightRatio 3
|
||||
|
||||
static void schedPriorityVerifierThread(void)
|
||||
{
|
||||
/* threadSleep() only flips this thread's own state to Sleeping; unlike
|
||||
the sysSleep syscall path (syscall.c), which forces a reschedule
|
||||
synchronously in the same interrupt frame right after calling it,
|
||||
a plain kernel thread body must yield for itself or it simply falls
|
||||
through to the next line on its own timeslice, sleep or not. Loop on
|
||||
halt() until schedule()'s "wake sleeping threads whose time has come"
|
||||
pass (sched.c) actually flips the state back, the same wait pattern
|
||||
pipeReadBlocking/pipeWriteBlocking (pipe.c) use for wait-queue waits:
|
||||
any spurious wake (e.g. a keyboard IRQ, which never calls schedule()
|
||||
and so never touches this state) just loops back into another
|
||||
halt() instead of proceeding early. */
|
||||
threadSleep(schedPriorityCheckDelayMs);
|
||||
while (currentThread()->state == threadSleeping)
|
||||
halt();
|
||||
|
||||
uint64 a = workerAIterations;
|
||||
uint64 b = workerBIterations;
|
||||
/* workerA runs at schedPriorityWeightRatio times workerB's weight, so
|
||||
over any sufficiently long stretch where both are always runnable
|
||||
(true here: both are pure CPU hogs, never sleeping or blocking) the
|
||||
CFS-lite scheduler should give workerA that many times as many turns.
|
||||
Allow a wide [1.5x, 6x] band rather than pinning to exactly 3x: this
|
||||
is a real preemptive scheduler racing against wall-clock QEMU
|
||||
performance, not a deterministic simulation, so the achieved ratio
|
||||
will vary run to run - the assertion that matters is "meaningfully
|
||||
and consistently favors the higher-weight thread", not an exact
|
||||
number. */
|
||||
bool ok = b > 0 && a > b + b / 2 && a < b * (schedPriorityWeightRatio * 2);
|
||||
kprintf("[sched] priority check: workerA=%lu workerB=%lu (weight ratio %u) -> %s\n",
|
||||
a, b, schedPriorityWeightRatio, ok ? "sched-priority-ok" : "sched-priority-FAIL");
|
||||
|
||||
for (;;)
|
||||
halt();
|
||||
}
|
||||
|
||||
#define pipeDemoMessages 10
|
||||
|
||||
static int pipeDemoHandle = -1;
|
||||
|
||||
static void pipeProducerThread(void)
|
||||
{
|
||||
for (int i = 0; i < pipeDemoMessages; i++) {
|
||||
char line[32];
|
||||
int len = snprintf(line, sizeof(line), "msg %d\n", i);
|
||||
|
||||
uint64 offset = 0;
|
||||
while (offset < (uint64)len) {
|
||||
int64 n = pipeWriteBlocking(pipeDemoHandle, line + offset, (uint64)len - offset);
|
||||
if (n <= 0)
|
||||
break;
|
||||
offset += (uint64)n;
|
||||
}
|
||||
}
|
||||
pipeCloseWrite(pipeDemoHandle);
|
||||
|
||||
/* Kernel thread entries run atop a synthetic iret frame, not a normal
|
||||
call stack: falling off the end here would pop garbage as the next
|
||||
instruction pointer. Every kernel thread in this scheduler must park
|
||||
itself forever instead of returning (see workerA/workerB). */
|
||||
for (;;)
|
||||
halt();
|
||||
}
|
||||
|
||||
static void pipeConsumerThread(void)
|
||||
{
|
||||
char lineBuffer[32];
|
||||
uint64 lineLength = 0;
|
||||
int received = 0;
|
||||
|
||||
for (;;) {
|
||||
char chunk[16];
|
||||
int64 n = pipeReadBlocking(pipeDemoHandle, chunk, sizeof(chunk));
|
||||
|
||||
if (n > 0) {
|
||||
for (int64 i = 0; i < n; i++) {
|
||||
char c = chunk[i];
|
||||
if (c == '\n') {
|
||||
lineBuffer[lineLength] = 0;
|
||||
kprintf("[pipe] consumer got: %s\n", lineBuffer);
|
||||
received++;
|
||||
lineLength = 0;
|
||||
} else if (lineLength + 1 < sizeof(lineBuffer)) {
|
||||
lineBuffer[lineLength++] = c;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* 0 = true EOF, -1 = invalid handle: either way there is
|
||||
nothing left to wait for. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pipeCloseRead(pipeDemoHandle);
|
||||
kprintf("pipe demo: eof, count=%d\n", received);
|
||||
|
||||
for (;;)
|
||||
halt();
|
||||
}
|
||||
|
||||
/* Demonstrates threadJoin/threadReap's real, live blocking path (sched.c,
|
||||
sched.h) end to end -- the one path schedTest.c cannot exercise, since
|
||||
ktestRunAll() runs before the scheduler is live and there is no second
|
||||
thread to actually park on and be woken by. shortLivedWorkerThread does a
|
||||
little busy-work then calls threadExit() directly (a kernel thread may
|
||||
call it as a plain function -- no syscall needed, unlike a ring-3
|
||||
program) -- joinerThread almost certainly starts first (both are created
|
||||
with default weight, and the worker has real work to do before it can
|
||||
exit), so this also proves the schedBlockCurrent+halt() park actually
|
||||
happens and schedWakeChannel actually wakes it, not just the fast "already
|
||||
dead" path. */
|
||||
#define joinDemoBusyIterations 20000000ull
|
||||
#define joinDemoExitCode 42ull
|
||||
|
||||
static volatile uint32 joinDemoWorkerId = threadInvalidId;
|
||||
|
||||
static void shortLivedWorkerThread(void)
|
||||
{
|
||||
for (volatile uint64 i = 0; i < joinDemoBusyIterations; i++)
|
||||
;
|
||||
threadExit(joinDemoExitCode);
|
||||
|
||||
/* Marked Dead above; the CPU is still executing this thread's code
|
||||
until the next timer tick preempts it into schedule(), which will
|
||||
never pick a Dead thread again. Must park, not return (see
|
||||
workerA/workerB). */
|
||||
for (;;)
|
||||
halt();
|
||||
}
|
||||
|
||||
static void joinProbeThread(void)
|
||||
{
|
||||
for (;;)
|
||||
halt();
|
||||
}
|
||||
|
||||
static void joinerThread(void)
|
||||
{
|
||||
int64 code = threadJoin(joinDemoWorkerId);
|
||||
bool codeOk = (code == (int64)joinDemoExitCode);
|
||||
|
||||
/* threadJoin already reaped the worker's slot -- prove it is genuinely
|
||||
reusable, not just marked dead, by immediately creating a new thread
|
||||
and checking it lands in that exact freed slot rather than growing
|
||||
the table further. */
|
||||
uint32 reused = threadCreate("joinReuseProbe", joinProbeThread);
|
||||
bool reuseOk = (reused == joinDemoWorkerId);
|
||||
if (reused != threadInvalidId)
|
||||
threadDiscard(reused);
|
||||
|
||||
kprintf("[sched] join demo: worker pid=%u exitCode=%ld (expected %lu), "
|
||||
"freed slot reused=%s -> %s\n",
|
||||
joinDemoWorkerId, code, joinDemoExitCode, reuseOk ? "yes" : "no",
|
||||
(codeOk && reuseOk) ? "join-reap-ok" : "join-reap-FAIL");
|
||||
|
||||
for (;;)
|
||||
halt();
|
||||
}
|
||||
|
||||
static void runHeapTest(void)
|
||||
{
|
||||
kprintf("[heap] free-list allocator self-test:\n");
|
||||
@@ -116,12 +291,12 @@ static void runPagingTest(void)
|
||||
kprintf("[paging] readback FAILED (value=%lx resolved=%lx)\n", *probe, resolved);
|
||||
}
|
||||
|
||||
static void spawnUser(const char* path, const char* name, uint64 stackTop, uint64 brkStart)
|
||||
static uint32 spawnUser(const char* path, const char* name, uint64 stackTop, uint64 brkStart)
|
||||
{
|
||||
VfsNode* node = vfsLookup(path);
|
||||
if (!node) {
|
||||
kprintf("[user] '%s' not found in vfs\n", path);
|
||||
return;
|
||||
return threadInvalidId;
|
||||
}
|
||||
|
||||
const void* image = vfsFileData(node);
|
||||
@@ -129,7 +304,7 @@ static void spawnUser(const char* path, const char* name, uint64 stackTop, uint6
|
||||
uint64 entry = elfLoad(image, size);
|
||||
if (entry == 0) {
|
||||
kprintf("[user] '%s' failed to load\n", path);
|
||||
return;
|
||||
return threadInvalidId;
|
||||
}
|
||||
|
||||
pagingMapUserRange(stackTop - userStackSize, userStackSize);
|
||||
|
||||
Reference in new issue
Block a user