diff --git a/src/kmain.c b/src/kmain.c index b209fa8..cc35d37 100644 --- a/src/kmain.c +++ b/src/kmain.c @@ -504,6 +504,179 @@ static void pgroupInjectorThread(void) #define scancodeMakeP 0x19 #define scancodeMakeI 0x17 #define scancodeMakeE 0x12 +#define scancodeMakeS 0x1F +#define scancodeMakeR 0x13 +#define scancodeMakeN 0x31 +#define scancodeMakeK 0x25 +#define scancodeMakeBackslash 0x2B + +static void injectKeyPress(uint8 makeCode) +{ + keyboardHandleScancode(makeCode); + keyboardHandleScancode(makeCode | scancodeBreakBit); +} + +/* '&' is shift+7 on a real US keyboard -- keyboard.c's scancodeUpper table + maps index scancodeMakeSeven to '&' only while shiftHeld is true. */ +static void injectShiftedKeyPress(uint8 makeCode) +{ + keyboardHandleScancode(scancodeLeftShiftMake); + keyboardHandleScancode(makeCode); + keyboardHandleScancode(makeCode | scancodeBreakBit); + keyboardHandleScancode(scancodeLeftShiftBreak); +} + +/* A single bare Enter, unconditionally, before typing anything this thread + actually cares about: every keystroke keyboardHandleScancode decodes is + broadcast to EVERY open console reader (keyboard.c), the shell's own + included, so consoleInjectorThread's single synthetic 'k' (well before + this thread ever runs) is still sitting unflushed in the shell's own + line buffer -- there is no way for this thread to tell "a stray + character from an earlier, unrelated demo" apart from "something a real + user typed". One Enter here executes that "k" as a harmless unknown + command (shell.c's execute()) and guarantees an empty line buffer before + the real commands below, exactly like pressing Enter at a real prompt to + clear a garbled line. */ +static void jobctlTypeFlushLine(void) +{ + injectKeyPress(scancodeMakeEnter); +} + +static void jobctlTypeBackgroundCommand(void) +{ + injectKeyPress(scancodeMakeJ); + injectKeyPress(scancodeMakeO); + injectKeyPress(scancodeMakeB); + injectKeyPress(scancodeCPressMake); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeL); + injectKeyPress(scancodeMakeB); + injectKeyPress(scancodeMakeG); + injectKeyPress(scancodeMakeSpace); + injectShiftedKeyPress(scancodeMakeSeven); + injectKeyPress(scancodeMakeEnter); +} + +static void jobctlTypeForegroundCommand(void) +{ + injectKeyPress(scancodeMakeJ); + injectKeyPress(scancodeMakeO); + injectKeyPress(scancodeMakeB); + injectKeyPress(scancodeCPressMake); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeL); + injectKeyPress(scancodeMakeF); + injectKeyPress(scancodeMakeG); + injectKeyPress(scancodeMakeEnter); +} + +#define jobctlDriverInitialDelayMs 900 +#define jobctlDriverTypeSettleMs 150 +#define jobctlDriverForegroundSettleMs 350 +#define jobctlDriverSignalSettleMs 150 + +static void jobctlDriverThread(void) +{ + threadSleep(jobctlDriverInitialDelayMs); + while (currentThread()->state == threadSleeping) + halt(); + + kprintf("[kbd] typing shell commands for the on-demand job-control demo\n"); + jobctlTypeFlushLine(); + + /* "jobctlbg &" -- backgrounded, so shell.c's runProgram never blocks + on it: jobctlSpawnForeground/jobctlRunBackground's own load+spawn + work is synchronous kernel code, so by the time the shell has + actually consumed all of these buffered bytes (needs its own + timeslice; keyboardHandleScancode above only enqueues them) its + shellLastSpawnedPid() is already this job's real pid, not stale. */ + jobctlTypeBackgroundCommand(); + threadSleep(jobctlDriverTypeSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + uint32 jobctlbgPid = shellLastSpawnedPid(); + + /* "jobctlfg" -- foreground, so the shell thread itself parks inside + jobctlWaitForeground (threadJoin) the moment it processes this + line's own trailing Enter. shellLastSpawnedPid() is still safe to + read afterward: shell.c's runProgram sets it right after + jobctlSpawnForeground returns, BEFORE ever calling + jobctlWaitForeground, precisely so a live demo like this one is + never stuck waiting for a foreground job to exit before it can even + learn that job's own pid. The longer settle here (vs. the + background command above) gives jobctlfg.c itself real scheduler + time to reach its own blocking pipe read before Ctrl-C below -- + the same margin ratio pgroupInjectDelayMs/pgroupSettleDelayMs above + already established for an equivalent single-process case. */ + jobctlTypeForegroundCommand(); + threadSleep(jobctlDriverForegroundSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + uint32 jobctlfgPid = shellLastSpawnedPid(); + + if (jobctlbgPid == threadInvalidId || jobctlfgPid == threadInvalidId || + jobctlbgPid == jobctlfgPid) { + kprintf("[kbd] on-demand job-control demo skipped: jobctlbg/jobctlfg never started\n"); + for (;;) + halt(); + } + + kprintf("[kbd] injecting synthetic ctrl-c for the on-demand job-control demo\n"); + keyboardHandleScancode(scancodeLeftCtrlMake); + keyboardHandleScancode(scancodeCPressMake); + keyboardHandleScancode(scancodeCPressBreak); + keyboardHandleScancode(scancodeLeftCtrlBreak); + + /* Give the scheduler a real timeslice to actually deliver the + resulting sigInt to jobctlfg (early-wake + return-to-ring3), let + the now-parked shell thread's own threadJoin notice it Dead, and let + jobctlWaitForeground's reclaim (jobctl.c) run -- all real scheduled + work, not instantaneous. */ + threadSleep(jobctlDriverSignalSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + + /* The direct, causal proof that shell.c's runProgram/jobctl.c's + jobctlWaitForeground actually reclaimed the terminal for the shell's + OWN group the instant jobctlfg died, rather than leaving it + permanently pointed at a now-dead pgid the way nothing in this + kernel ever un-claimed it before this subsystem existed: if the + reclaim never ran (or ran against the wrong pgid), the foreground + would still equal jobctlfg's own now-meaningless pid forever. */ + uint32 fgAfterReclaim = keyboardForegroundPgid(); + bool reclaimed = (fgAfterReclaim != jobctlfgPid); + kprintf("[user] jobctl demo: jobctlfg pid=%u foreground pgid now=%u -> %s\n", + jobctlfgPid, fgAfterReclaim, + reclaimed ? "shell-foreground-reclaimed-ok" : "FAIL"); + + /* Proves jobctlbg (never granted the terminal at all, per shell.c's + `&` handling) is still genuinely alive and running real code after + the Ctrl-C above -- if the shell's background path ever put it in + jobctlfg's group (or granted it the foreground itself), it would + already be Dead by now and this sigRaise would silently do nothing, + so jobctlbg.c's own "still-alive-after-ctrlc-ok" needle would simply + never appear in the boot log, failing the gate. */ + sigRaise(jobctlbgPid, sigUsr1); + + for (;;) + halt(); +} + +#define pipelineDriverInitialDelayMs 2500 +#define pipelineDriverSettleMs 500 + +static void pipelineTypeCommand(void) +{ + injectKeyPress(scancodeMakeP); + injectKeyPress(scancodeMakeI); + injectKeyPress(scancodeMakeP); + injectKeyPress(scancodeMakeE); + injectKeyPress(scancodeMakeS); + injectKeyPress(scancodeMakeR); + injectKeyPress(scancodeCPressMake); + injectKeyPress(scancodeMakeSpace); + injectShiftedKeyPress(scancodeMakeBackslash); + injectKeyPress(scancodeMakeSpace); } void kmain(uint32 magic, uint64 multibootInfo)