From 21f27194136e8b088e7dccdbec9d87db7b7e732e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 6 Sep 2026 13:25:21 +0200 Subject: [PATCH] feat: extend kmain with lsDriverThread and andOrTypeFailChainCommand --- src/kmain.c | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/src/kmain.c b/src/kmain.c index 1d81545..40f8563 100644 --- a/src/kmain.c +++ b/src/kmain.c @@ -1715,6 +1715,126 @@ static void lsTypeLongListCommand(void) * rather than silently falling back to the "no timestamp" dash this same * code path prints for tmpfs/dev entries. */ +static void lsDriverThread(void) +{ + threadSleep(lsDriverInitialDelayMs); + while (currentThread()->state == threadSleeping) + halt(); + + kprintf("[kbd] typing shell commands for the ls -l demo\n"); + jobctlTypeFlushLine(); + + lsTypeLongListCommand(); + threadSleep(lsDriverStepSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + + for (;;) + halt(); +} + +/* 20500ms clears lsDriverThread's own total budget (19500 initial + 300 + settle = 19800ms) with 700ms margin -- smaller than most prior driver + threads' own margin since `ls -l` is a plain builtin (listDir, shell.c) + with no process spawn/VFS-blocking wait after its own settle delay, + unlike e.g. globDriverThread's globcat.c spawn+read+compare, so there is + no extra real execution time to wait out beyond the typing itself. Same + "next demo's initial delay clears the previous demo's total budget with + margin" convention every driver thread above establishes. Only ONE + command, ONE settle step -- false.c is the smallest possible spawned + program in this kernel (immediate userExit, no VFS/pipe/signal work at + all), and this fala's own live demo needs only one line of it (see + andOrTypeFailChainCommand's own comment for why one line already proves + the whole feature), the same budget-consciousness + lsDriverInitialDelayMs's own comment established: an EARLIER version of + this demo also typed a second line ("true && echo aoy", proving `&&` + fires on a real SUCCESS too) and was cut after it measurably pushed one + real re-run over tools/ci-test.sh's fixed 25-real-second QEMU ceiling + under this session's own elevated host load -- not worth the extra + margin lost for a case the KTEST-level shellSplitAndOr tests (which + assert both shellAndOrAnd and shellAndOrOr are parsed and recorded + correctly) and this project's dozens of pre-existing successful + `cmd && cmd`-free typed commands already make low-risk. */ +#define andOrDriverInitialDelayMs 20500 +#define andOrDriverStepSettleMs 300 + +/* "false && echo X || echo ao2" -- the classic shell if-then-else idiom in + one line. false.c genuinely spawns and exits 1 (see its own file + comment); `&&` sees that real nonzero status and SKIPS "echo X" (a + builtin, would otherwise print instantly); the `||` check right after + it is then evaluated against the SAME status "echo X" never changed + (shellSplitAndOr/execute()'s own "a skipped segment leaves status + completely unchanged" contract, shell.h) -- still nonzero, so it DOES + run "echo ao2". The needle `ao2` can only ever appear if both the skip + and the carry-through past that skip worked correctly; "X" is + deliberately never checked for (this kernel's tools/ci-test.sh only + ever asserts PRESENCE of a needle, never absence), so this line's own + affirmative proof is entirely in `ao2` actually appearing at all. Every + character this line needs (f/a/l/s/e/space/&/h/c/o/x/|/a/o/2) is already + defined by an earlier demo's own scancode block in this file. */ +static void andOrTypeFailChainCommand(void) +{ + injectKeyPress(scancodeMakeF); + injectKeyPress(scancodeMakeA); + injectKeyPress(scancodeMakeL); + injectKeyPress(scancodeMakeS); + injectKeyPress(scancodeMakeE); + injectKeyPress(scancodeMakeSpace); + injectShiftedKeyPress(scancodeMakeSeven); + injectShiftedKeyPress(scancodeMakeSeven); + injectKeyPress(scancodeMakeSpace); + injectKeyPress(scancodeMakeE); + injectKeyPress(scancodeCPressMake); + injectKeyPress(scancodeMakeH); + injectKeyPress(scancodeMakeO); + injectKeyPress(scancodeMakeSpace); + injectKeyPress(scancodeMakeX); + injectKeyPress(scancodeMakeSpace); + injectShiftedKeyPress(scancodeMakeBackslash); + injectShiftedKeyPress(scancodeMakeBackslash); + injectKeyPress(scancodeMakeSpace); + injectKeyPress(scancodeMakeE); + injectKeyPress(scancodeCPressMake); + injectKeyPress(scancodeMakeH); + injectKeyPress(scancodeMakeO); + injectKeyPress(scancodeMakeSpace); + injectKeyPress(scancodeMakeA); + injectKeyPress(scancodeMakeO); + injectKeyPress(scancodeMakeTwo); + injectKeyPress(scancodeMakeEnter); +} + +/* + * Live ring-3 proof for shell.c's new `cmd1 && cmd2 || cmd3 ...` + * conditional chaining (shellSplitAndOr, executeSingle/execute) -- types + * the command above at the real interactive shell, the same + * injected-keystroke mechanism every shell-driven demo above already + * uses. Unlike every earlier shell-syntax demo (pipelines, redirects, + * here-strings, globs), the actual subject under test here is false.c -- + * a brand-new, deliberately trivial user program whose entire job is to + * hand back a real, predictable, nonzero exit code from a genuine ring-3 + * spawn (see its own file comment) -- proving the new chaining logic + * reacts to a REAL process's own exit status, not just a builtin's + * in-kernel int return (which listDir/catFile/makeDir/changeDir/ + * doExport/doUnset already exercise on every OTHER typed command in this + * whole boot sequence, just never through `&&`/`||` before this fala). + */ +static void andOrDriverThread(void) +{ + threadSleep(andOrDriverInitialDelayMs); + while (currentThread()->state == threadSleeping) + halt(); + + kprintf("[kbd] typing shell commands for the &&/|| chaining demo\n"); + jobctlTypeFlushLine(); + + andOrTypeFailChainCommand(); + threadSleep(andOrDriverStepSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + + for (;;) + halt(); } void kmain(uint32 magic, uint64 multibootInfo) @@ -1756,6 +1876,12 @@ void kmain(uint32 magic, uint64 multibootInfo) runHeapTest(); runPagingTest(); + sigInstallTrampoline(); + kprintf("[sig] shared signal trampoline mapped at %p\n", (void*)sigTrampolineVirtualAddr); + + sigInstallInfoPage(); + kprintf("[sig] shared siginfo page(s) mapped at %p\n", (void*)sigInfoVirtualAddr); + pitInit(timerFrequency); kprintf("[pit] timer at %u Hz\n", timerFrequency); @@ -1773,9 +1899,61 @@ void kmain(uint32 magic, uint64 multibootInfo) kprintf("[vfs] no multiboot2 module present\n"); vfsMountTmpfs("/tmp"); + /* namedreader/namedwriter (spawned below, after userland launch) are two + INDEPENDENT boot programs with no parent-child relationship and no + shared argv -- unlike fifomaster.c's children, which get a pipe.h + handle number handed to them, these two only agree on this path. The + fifo has to exist before either one can open it. */ + if (vfsMkfifo("/tmp/rendezvous") == 0) + kprintf("[vfs] mkfifo /tmp/rendezvous ready\n"); + else + kprintf("[vfs] mkfifo /tmp/rendezvous FAILED\n"); + + if (ataInit()) + kprintf("[ata] primary master '%s', %lu sectors (%lu MiB), /dev/disk0 ready\n", + ataModelString(), ataSectorCount(), + (ataSectorCount() * ataSectorSize) / (1024 * 1024)); + else + kprintf("[ata] no primary master drive detected, /dev/disk0 will read as empty\n"); + + if (sfsMount()) + kprintf("[sfs] mounted persistent filesystem at /mnt (%lu KiB usable)\n", + sfsCapacityBytes() / 1024); + else + kprintf("[sfs] no usable disk for /mnt (drive missing or too small)\n"); + + ktestRunAll(); + threadCreate("shell", shellThread); - threadCreate("workerA", workerA); - threadCreate("workerB", workerB); + uint32 workerAId = threadCreate("workerA", workerA); + uint32 workerBId = threadCreate("workerB", workerB); + + /* Both workers are pure CPU hogs (busy loops, never sleep or block), so + they are the cleanest possible demonstration that the CFS-lite + scheduler (sched.c/prioq.h) actually enforces weight, not just that it + compiles: workerA is given 3x workerB's weight, so it should accrue + vruntime three times slower and therefore win the ready-queue's + min-vruntime pick roughly three times as often. Compare the two + threads' printed iteration counts at similar tick values to see it. */ + threadSetPriority(workerAId, schedDefaultWeight * schedPriorityWeightRatio); + kprintf("[sched] workerA pid=%u weight=%u, workerB pid=%u weight=%u " + "(CFS-lite: higher weight runs more often)\n", + workerAId, schedDefaultWeight * schedPriorityWeightRatio, workerBId, schedDefaultWeight); + threadCreate("schedVerify", schedPriorityVerifierThread); + + pipeDemoHandle = pipeCreate(); + if (pipeDemoHandle >= 0) { + threadCreate("pipeProducer", pipeProducerThread); + threadCreate("pipeConsumer", pipeConsumerThread); + } else { + kprintf("[pipe] demo pipe creation failed\n"); + } + + joinDemoWorkerId = threadCreate("joinWorker", shortLivedWorkerThread); + if (joinDemoWorkerId != threadInvalidId) + threadCreate("joiner", joinerThread); + else + kprintf("[sched] join demo worker not started: thread table full\n"); kprintf("\n[boot] launching userland programs from the ramdisk into ring 3\n"); spawnUser("/bin/hello", "hello", 0x48000000, 0);