feat: extend kmain with execReaperThread and consoleInjectorThread

This commit is contained in:
dualfroz committed 2026-09-04 19:14:06 +02:00
1 parent 661ff83253
commit b4f8d5215f
1 file changed
+192 -2
+192 -2
View File
@@ -307,13 +307,203 @@ static uint32 spawnUser(const char* path, const char* name, uint64 stackTop, uin
return threadInvalidId; return threadInvalidId;
} }
pagingMapUserRange(stackTop - userStackSize, userStackSize); pagingMapUserRangeFlags(stackTop - userStackSize, userStackSize, pageNoExecute);
uint32 pid = threadCreateUser(name, entry, stackTop);
/* Every boot-launched program gets a real, if minimal, argv: just its
own path, matching what a real OS would put in argv[0]. Nothing in
this kernel's boot sequence needs more than that -- programs wanting
specific arguments are spawned at runtime instead (doSpawn,
syscall.c), where the caller controls argv directly. */
const char* argv[1] = { path };
uint64 initialRsp;
if (!elfBuildInitialStack(stackTop, userStackSize, argv, 1, &initialRsp)) {
kprintf("[user] '%s' not started: argv did not fit its stack\n", path);
return threadInvalidId;
}
uint32 pid = threadCreateUser(name, entry, initialRsp);
if (pid == threadInvalidId) {
kprintf("[user] '%s' not started: thread table full\n", path);
return threadInvalidId;
}
if (brkStart) if (brkStart)
threadSetBrk(pid, brkStart); threadSetBrk(pid, brkStart);
kprintf("[user] loaded '%s' pid=%u entry=%p stackTop=%p (%lu bytes)\n", kprintf("[user] loaded '%s' pid=%u entry=%p stackTop=%p (%lu bytes)\n",
name, pid, (void*)entry, (void*)stackTop, size); name, pid, (void*)entry, (void*)stackTop, size);
return pid;
}
/* proctest.c's very last action on its success path is userExec(), which
replaces its own image with exectarget.c's -- so the only way to observe
from OUTSIDE that process that exec() genuinely happened (rather than
proctest simply exiting cleanly on its own) is to threadJoin() the exact
pid kmain.c originally gave proctest and check that its eventual exit
code is exectarget's (7), not proctest's own success path (which never
returns 7 -- it either exits 1 on a self-detected FAIL or never exits at
all once exec succeeds). A mismatch or an exit code belonging to neither
program both surface as a plain FAIL below. */
#define execDemoExitCode 7ull
static uint32 execDemoPid = threadInvalidId;
static void execReaperThread(void)
{
if (execDemoPid == threadInvalidId) {
kprintf("[user] exec demo skipped: proctest never started\n");
for (;;)
halt();
}
int64 code = threadJoin(execDemoPid);
bool ok = (code == (int64)execDemoExitCode);
kprintf("[user] exec demo: pid=%u final exitCode=%ld (expected %lu) -> %s\n",
execDemoPid, code, execDemoExitCode, ok ? "exec-pid-preserved-ok" : "exec-pid-preserved-FAIL");
for (;;)
halt();
}
/* Headless QEMU has no automated way to drive a real PS/2 keyboard device --
ci-test.sh only captures the COM1 serial log, and scripts no QEMU monitor
"sendkey" command -- so this thread simulates exactly one keystroke the
same way a real IRQ1 handler would: by calling keyboardHandleScancode
(keyboard.h) directly with the scancode a real 'k' keypress generates.
consolereader1.c and consolereader2.c are both already parked in a real,
blocking userRead(0, ...) well before this fires (the sleep below gives
every boot-time thread, including both of them, at least one CFS-lite
timeslice first) -- keyboard.c's per-reader fan-out then delivers each of
them its own independent copy of the same byte, proving two entirely
unrelated ring-3 processes can share "the keyboard" without racing each
other for it, the gap the previous wave's own notes called out as the
next logical step after pipes. */
#define consoleDemoScancodeK 0x25
#define consoleInjectDelayMs 250
static void consoleInjectorThread(void)
{
threadSleep(consoleInjectDelayMs);
while (currentThread()->state == threadSleeping)
halt();
kprintf("[kbd] injecting synthetic keypress 'k' for the console fan-out demo\n");
keyboardHandleScancode(consoleDemoScancodeK);
for (;;)
halt();
}
/* Same "headless QEMU cannot drive a real PS/2 device" reasoning as
consoleInjectorThread above, this time simulating a real Ctrl-C
keypress (left-Ctrl held, 'c' pressed and released, left-Ctrl released)
by calling keyboardHandleScancode with exactly the four scancodes a
physical keyboard would generate for it -- the live ring-3 proof that
sched.h's new Thread.pgid and keyboard.c's own new INTR interception
actually scope a terminal-generated signal to ONE process group
(pgroupfg.c, made the foreground below) and leave a completely
different one (pgroupbg.c, deliberately left as its own default group)
untouched. pgroupfgPid/pgroupbgPid are threadInvalidId if either
program failed to load (thread table full, missing from the ramdisk) --
this thread degrades to a silent no-op rather than acting on a bogus id,
the same "skipped" pattern execReaperThread already uses for its own
demo. */
#define scancodeLeftCtrlMake 0x1D
#define scancodeLeftCtrlBreak 0x9D
#define scancodeCPressMake 0x2E
#define scancodeCPressBreak 0xAE
#define pgroupInjectDelayMs 400
#define pgroupSettleDelayMs 100
static uint32 pgroupfgPid = threadInvalidId;
static uint32 pgroupbgPid = threadInvalidId;
static void pgroupInjectorThread(void)
{
if (pgroupfgPid == threadInvalidId || pgroupbgPid == threadInvalidId) {
kprintf("[kbd] process-group demo skipped: pgroupfg/pgroupbg never started\n");
for (;;)
halt();
}
threadSleep(pgroupInjectDelayMs);
while (currentThread()->state == threadSleeping)
halt();
kprintf("[kbd] injecting synthetic ctrl-c for the process-group demo\n");
keyboardHandleScancode(scancodeLeftCtrlMake);
keyboardHandleScancode(scancodeCPressMake);
keyboardHandleScancode(scancodeCPressBreak);
keyboardHandleScancode(scancodeLeftCtrlBreak);
/* Give the scheduler a real timeslice to actually deliver the
resulting sigInt to pgroupfg (early-wake + return-to-ring3, not
instantaneous) before this thread moves on to check anything. */
threadSleep(pgroupSettleDelayMs);
while (currentThread()->state == threadSleeping)
halt();
int64 fgCode = threadJoin(pgroupfgPid);
bool fgOk = (fgCode == (int64)(128 + sigInt));
kprintf("[user] pgroup demo: fg pid=%u final exitCode=%ld (expected 128+sigInt=%d) -> %s\n",
pgroupfgPid, fgCode, 128 + sigInt, fgOk ? "pgrouptest-fg-terminated-by-sigint-ok" : "FAIL");
/* Proves pgroupbg (a DIFFERENT process group, never named as
foreground) is still genuinely alive and running real code after
the Ctrl-C above -- if keyboard.c's group scoping were ever broken
to reach it too, pgroupbg would already be Dead by now and this
sigRaise (signal.h) would silently do nothing, so pgroupbg.c's own
"still-alive-after-ctrlc-ok" needle would simply never appear in
the boot log, failing the gate. */
sigRaise(pgroupbgPid, sigUsr1);
for (;;)
halt();
}
/*
* The live ring-3 proof for shell.c's new `NAME [ARGS] [&]` job control
* (jobctl.h does the actual spawning/terminal-ownership work): rather than
* spawning jobctlfg.c/jobctlbg.c directly the way every demo above does,
* this thread TYPES commands at the real interactive shell (shellRun,
* already running as its own boot-time thread) through the same
* injected-keystroke mechanism consoleInjectorThread/pgroupInjectorThread
* above already use for the same "no automated way to drive a real PS/2
* device under headless QEMU" reason -- proving the shell's own runtime
* decision of what to load and whether to grant it the terminal, not just
* that jobctl.c's functions work when called directly.
*
* Scancode values below are read straight off keyboard.c's own
* scancodeLower table (index N there is scancode N here -- already cross-
* checked against that file's own worked examples: index 0x25 is 'k',
* exactly consoleDemoScancodeK above; index 0x2E is 'c', exactly
* scancodeCPressMake above), not guessed.
*/
#define scancodeMakeJ 0x24
#define scancodeMakeO 0x18
#define scancodeMakeB 0x30
#define scancodeMakeT 0x14
#define scancodeMakeL 0x26
#define scancodeMakeG 0x22
#define scancodeMakeF 0x21
#define scancodeMakeSpace 0x39
#define scancodeMakeSeven 0x08
#define scancodeMakeEnter 0x1C
#define scancodeLeftShiftMake 0x2A
#define scancodeLeftShiftBreak 0xAA
#define scancodeBreakBit 0x80
/* Additional letters/symbol needed only by pipelineDriverThread's own
"pipesrc | pipesink" command below -- same cross-checked-against-
scancodeLower sourcing as the block above (index 0x19 is 'p', 0x17 is
'i', 0x12 is 'e', 0x1F is 's', 0x13 is 'r', 0x31 is 'n', 0x25 is 'k'
(equal to consoleDemoScancodeK above, kept as this file's own named copy
for this demo, the same "each caller of this pattern owns its own named
copy" choice jobctl.c/kmain.c/syscall.c already make elsewhere), 0x2B is
'\\', shifted into '|' by scancodeUpper the same way scancodeMakeSeven
is shifted into '&' above). */
#define scancodeMakeP 0x19
#define scancodeMakeI 0x17
#define scancodeMakeE 0x12
} }
void kmain(uint32 magic, uint64 multibootInfo) void kmain(uint32 magic, uint64 multibootInfo)