feat: extend kmain with execReaperThread and consoleInjectorThread
This commit is contained in:
1 parent
661ff83253
commit
b4f8d5215f
1 file changed
+192
-2
+192
-2
@@ -307,13 +307,203 @@ static uint32 spawnUser(const char* path, const char* name, uint64 stackTop, uin
|
||||
return threadInvalidId;
|
||||
}
|
||||
|
||||
pagingMapUserRange(stackTop - userStackSize, userStackSize);
|
||||
uint32 pid = threadCreateUser(name, entry, stackTop);
|
||||
pagingMapUserRangeFlags(stackTop - userStackSize, userStackSize, pageNoExecute);
|
||||
|
||||
/* 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)
|
||||
threadSetBrk(pid, brkStart);
|
||||
|
||||
kprintf("[user] loaded '%s' pid=%u entry=%p stackTop=%p (%lu bytes)\n",
|
||||
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)
|
||||
|
||||
Reference in new issue
Block a user