feat: extend kmain with lsDriverThread and andOrTypeFailChainCommand

This commit is contained in:
dualfroz committed 2026-09-06 13:25:21 +02:00
1 parent 68907b39ec
commit 21f2719413
1 file changed
+180 -2
+180 -2
View File
@@ -1715,6 +1715,126 @@ static void lsTypeLongListCommand(void)
* rather than silently falling back to the "no timestamp" dash this same * rather than silently falling back to the "no timestamp" dash this same
* code path prints for tmpfs/dev entries. * 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) void kmain(uint32 magic, uint64 multibootInfo)
@@ -1756,6 +1876,12 @@ void kmain(uint32 magic, uint64 multibootInfo)
runHeapTest(); runHeapTest();
runPagingTest(); 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); pitInit(timerFrequency);
kprintf("[pit] timer at %u Hz\n", 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"); kprintf("[vfs] no multiboot2 module present\n");
vfsMountTmpfs("/tmp"); 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("shell", shellThread);
threadCreate("workerA", workerA); uint32 workerAId = threadCreate("workerA", workerA);
threadCreate("workerB", workerB); 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"); kprintf("\n[boot] launching userland programs from the ramdisk into ring 3\n");
spawnUser("/bin/hello", "hello", 0x48000000, 0); spawnUser("/bin/hello", "hello", 0x48000000, 0);