2075 lines
84 KiB
C
2075 lines
84 KiB
C
#include "types.h"
|
|
#include "console.h"
|
|
#include "gdt.h"
|
|
#include "idt.h"
|
|
#include "tss.h"
|
|
#include "pic.h"
|
|
#include "pit.h"
|
|
#include "keyboard.h"
|
|
#include "pmm.h"
|
|
#include "paging.h"
|
|
#include "heap.h"
|
|
#include "sched.h"
|
|
#include "shell.h"
|
|
#include "multiboot2.h"
|
|
#include "initrd.h"
|
|
#include "vfs.h"
|
|
#include "elf.h"
|
|
#include "ktest.h"
|
|
#include "pipe.h"
|
|
#include "klib.h"
|
|
#include "signal.h"
|
|
#include "ports.h"
|
|
#include "ata.h"
|
|
#include "sfs.h"
|
|
|
|
#define multibootBootMagic 0x36D76289
|
|
#define heapSize (48 * 1024 * 1024)
|
|
#define timerFrequency 100
|
|
#define userStackSize 0x4000
|
|
|
|
static uint8 bootstrapKernelStack[16384] __attribute__((aligned(16)));
|
|
|
|
static void banner(void)
|
|
{
|
|
kprintf("\n");
|
|
kprintf("========================================\n");
|
|
kprintf(" frostbyte kernel x86_64 long mode\n");
|
|
kprintf(" wave 2: userspace, ring 3, syscalls\n");
|
|
kprintf("========================================\n\n");
|
|
}
|
|
|
|
static void shellThread(void)
|
|
{
|
|
shellRun();
|
|
}
|
|
|
|
/* Exposed at file scope (rather than local to workerA/workerB) so
|
|
schedPriorityVerifierThread can read them back and turn "the scheduler
|
|
favors higher weight" into an automated, gate-checked assertion instead
|
|
of something a human has to eyeball in the boot log. */
|
|
static volatile uint64 workerAIterations;
|
|
static volatile uint64 workerBIterations;
|
|
|
|
static void workerA(void)
|
|
{
|
|
uint64 nextLog = 50;
|
|
for (;;) {
|
|
workerAIterations++;
|
|
if (pitTicks() >= nextLog) {
|
|
kprintf("[workerA] tick=%lu iterations=%lu\n", pitTicks(), workerAIterations);
|
|
nextLog += 100;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void workerB(void)
|
|
{
|
|
uint64 nextLog = 90;
|
|
for (;;) {
|
|
workerBIterations++;
|
|
if (pitTicks() >= nextLog) {
|
|
kprintf("[workerB] tick=%lu iterations=%lu\n", pitTicks(), workerBIterations);
|
|
nextLog += 100;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define schedPriorityCheckDelayMs 15000
|
|
#define schedPriorityWeightRatio 3
|
|
|
|
static void schedPriorityVerifierThread(void)
|
|
{
|
|
/* threadSleep() only flips this thread's own state to Sleeping; unlike
|
|
the sysSleep syscall path (syscall.c), which forces a reschedule
|
|
synchronously in the same interrupt frame right after calling it,
|
|
a plain kernel thread body must yield for itself or it simply falls
|
|
through to the next line on its own timeslice, sleep or not. Loop on
|
|
halt() until schedule()'s "wake sleeping threads whose time has come"
|
|
pass (sched.c) actually flips the state back, the same wait pattern
|
|
pipeReadBlocking/pipeWriteBlocking (pipe.c) use for wait-queue waits:
|
|
any spurious wake (e.g. a keyboard IRQ, which never calls schedule()
|
|
and so never touches this state) just loops back into another
|
|
halt() instead of proceeding early. */
|
|
threadSleep(schedPriorityCheckDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
uint64 a = workerAIterations;
|
|
uint64 b = workerBIterations;
|
|
/* workerA runs at schedPriorityWeightRatio times workerB's weight, so
|
|
over any sufficiently long stretch where both are always runnable
|
|
(true here: both are pure CPU hogs, never sleeping or blocking) the
|
|
CFS-lite scheduler should give workerA that many times as many turns.
|
|
Allow a wide [1.5x, 6x] band rather than pinning to exactly 3x: this
|
|
is a real preemptive scheduler racing against wall-clock QEMU
|
|
performance, not a deterministic simulation, so the achieved ratio
|
|
will vary run to run - the assertion that matters is "meaningfully
|
|
and consistently favors the higher-weight thread", not an exact
|
|
number. */
|
|
bool ok = b > 0 && a > b + b / 2 && a < b * (schedPriorityWeightRatio * 2);
|
|
kprintf("[sched] priority check: workerA=%lu workerB=%lu (weight ratio %u) -> %s\n",
|
|
a, b, schedPriorityWeightRatio, ok ? "sched-priority-ok" : "sched-priority-FAIL");
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
#define pipeDemoMessages 10
|
|
|
|
static int pipeDemoHandle = -1;
|
|
|
|
static void pipeProducerThread(void)
|
|
{
|
|
for (int i = 0; i < pipeDemoMessages; i++) {
|
|
char line[32];
|
|
int len = snprintf(line, sizeof(line), "msg %d\n", i);
|
|
|
|
uint64 offset = 0;
|
|
while (offset < (uint64)len) {
|
|
int64 n = pipeWriteBlocking(pipeDemoHandle, line + offset, (uint64)len - offset);
|
|
if (n <= 0)
|
|
break;
|
|
offset += (uint64)n;
|
|
}
|
|
}
|
|
pipeCloseWrite(pipeDemoHandle);
|
|
|
|
/* Kernel thread entries run atop a synthetic iret frame, not a normal
|
|
call stack: falling off the end here would pop garbage as the next
|
|
instruction pointer. Every kernel thread in this scheduler must park
|
|
itself forever instead of returning (see workerA/workerB). */
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
static void pipeConsumerThread(void)
|
|
{
|
|
char lineBuffer[32];
|
|
uint64 lineLength = 0;
|
|
int received = 0;
|
|
|
|
for (;;) {
|
|
char chunk[16];
|
|
int64 n = pipeReadBlocking(pipeDemoHandle, chunk, sizeof(chunk));
|
|
|
|
if (n > 0) {
|
|
for (int64 i = 0; i < n; i++) {
|
|
char c = chunk[i];
|
|
if (c == '\n') {
|
|
lineBuffer[lineLength] = 0;
|
|
kprintf("[pipe] consumer got: %s\n", lineBuffer);
|
|
received++;
|
|
lineLength = 0;
|
|
} else if (lineLength + 1 < sizeof(lineBuffer)) {
|
|
lineBuffer[lineLength++] = c;
|
|
}
|
|
}
|
|
} else {
|
|
/* 0 = true EOF, -1 = invalid handle: either way there is
|
|
nothing left to wait for. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
pipeCloseRead(pipeDemoHandle);
|
|
kprintf("pipe demo: eof, count=%d\n", received);
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/* Demonstrates threadJoin/threadReap's real, live blocking path (sched.c,
|
|
sched.h) end to end -- the one path schedTest.c cannot exercise, since
|
|
ktestRunAll() runs before the scheduler is live and there is no second
|
|
thread to actually park on and be woken by. shortLivedWorkerThread does a
|
|
little busy-work then calls threadExit() directly (a kernel thread may
|
|
call it as a plain function -- no syscall needed, unlike a ring-3
|
|
program) -- joinerThread almost certainly starts first (both are created
|
|
with default weight, and the worker has real work to do before it can
|
|
exit), so this also proves the schedBlockCurrent+halt() park actually
|
|
happens and schedWakeChannel actually wakes it, not just the fast "already
|
|
dead" path. */
|
|
#define joinDemoBusyIterations 20000000ull
|
|
#define joinDemoExitCode 42ull
|
|
|
|
static volatile uint32 joinDemoWorkerId = threadInvalidId;
|
|
|
|
static void shortLivedWorkerThread(void)
|
|
{
|
|
for (volatile uint64 i = 0; i < joinDemoBusyIterations; i++)
|
|
;
|
|
threadExit(joinDemoExitCode);
|
|
|
|
/* Marked Dead above; the CPU is still executing this thread's code
|
|
until the next timer tick preempts it into schedule(), which will
|
|
never pick a Dead thread again. Must park, not return (see
|
|
workerA/workerB). */
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
static void joinProbeThread(void)
|
|
{
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
static void joinerThread(void)
|
|
{
|
|
int64 code = threadJoin(joinDemoWorkerId);
|
|
bool codeOk = (code == (int64)joinDemoExitCode);
|
|
|
|
/* threadJoin already reaped the worker's slot -- prove it is genuinely
|
|
reusable, not just marked dead, by immediately creating a new thread
|
|
and checking it lands in that exact freed slot rather than growing
|
|
the table further. */
|
|
uint32 reused = threadCreate("joinReuseProbe", joinProbeThread);
|
|
bool reuseOk = (reused == joinDemoWorkerId);
|
|
if (reused != threadInvalidId)
|
|
threadDiscard(reused);
|
|
|
|
kprintf("[sched] join demo: worker pid=%u exitCode=%ld (expected %lu), "
|
|
"freed slot reused=%s -> %s\n",
|
|
joinDemoWorkerId, code, joinDemoExitCode, reuseOk ? "yes" : "no",
|
|
(codeOk && reuseOk) ? "join-reap-ok" : "join-reap-FAIL");
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
static void runHeapTest(void)
|
|
{
|
|
kprintf("[heap] free-list allocator self-test:\n");
|
|
|
|
void* a = kmalloc(100);
|
|
void* b = kmalloc(200);
|
|
void* c = kmalloc(100);
|
|
kprintf("[heap] alloc a=%p b=%p c=%p, live blocks=%lu\n",
|
|
a, b, c, heapBlockCount());
|
|
|
|
kfree(b);
|
|
void* d = kmalloc(150);
|
|
kprintf("[heap] free(b) then malloc(150) -> d=%p, reused b: %s\n",
|
|
d, d == b ? "yes" : "no");
|
|
|
|
uint64 beforeCoalesce = heapLargestFree();
|
|
kfree(a);
|
|
kfree(c);
|
|
kfree(d);
|
|
uint64 afterCoalesce = heapLargestFree();
|
|
|
|
kprintf("[heap] largest free %lu KiB -> %lu KiB after freeing a,c,d\n",
|
|
beforeCoalesce / 1024, afterCoalesce / 1024);
|
|
kprintf("[heap] coalesce %s, %lu KiB free of %lu KiB, blocks=%lu\n",
|
|
afterCoalesce > beforeCoalesce ? "PASS" : "FAIL",
|
|
heapFreeBytes() / 1024, heapTotal() / 1024, heapBlockCount());
|
|
}
|
|
|
|
static void runPagingTest(void)
|
|
{
|
|
uint64 testVirtual = 0xC0000000;
|
|
uint64 frame = pmmAlloc();
|
|
|
|
if (frame == 0) {
|
|
kprintf("[paging] no free frame for test\n");
|
|
return;
|
|
}
|
|
|
|
if (!pagingMap(testVirtual, frame, pagePresent | pageWrite)) {
|
|
kprintf("[paging] mapping failed\n");
|
|
return;
|
|
}
|
|
|
|
volatile uint64* probe = (volatile uint64*)testVirtual;
|
|
*probe = 0xCAFEBABEDEADBEEFull;
|
|
|
|
uint64 resolved = pagingResolve(testVirtual);
|
|
if (*probe == 0xCAFEBABEDEADBEEFull && resolved == frame)
|
|
kprintf("[paging] mapped %p -> phys %lx, readback OK\n", (void*)testVirtual, frame);
|
|
else
|
|
kprintf("[paging] readback FAILED (value=%lx resolved=%lx)\n", *probe, resolved);
|
|
}
|
|
|
|
static uint32 spawnUser(const char* path, const char* name, uint64 stackTop, uint64 brkStart)
|
|
{
|
|
VfsNode* node = vfsLookup(path);
|
|
if (!node) {
|
|
kprintf("[user] '%s' not found in vfs\n", path);
|
|
return threadInvalidId;
|
|
}
|
|
|
|
const void* image = vfsFileData(node);
|
|
uint64 size = vfsSize(node);
|
|
uint64 entry = elfLoad(image, size);
|
|
if (entry == 0) {
|
|
kprintf("[user] '%s' failed to load\n", path);
|
|
return threadInvalidId;
|
|
}
|
|
|
|
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
|
|
#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);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeN);
|
|
injectKeyPress(scancodeMakeK);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
/*
|
|
* The live ring-3 proof for shell.c's new `cmd1 | cmd2` pipeline syntax
|
|
* (jobctl.h's jobctlSpawnPiped/jobctlWaitPipelineStages do the actual
|
|
* fd-baking and multi-wait work): types "pipesrc | pipesink" at the real interactive
|
|
* shell through the same injected-keystroke mechanism jobctlDriverThread
|
|
* above already uses, well after that thread's own demo has fully
|
|
* finished with the shell's shared line buffer (both threads' typed
|
|
* characters go through the exact same broadcast keyboardHandleScancode
|
|
* path, so they cannot safely interleave -- pipelineDriverInitialDelayMs
|
|
* is chosen with a comfortable margin over jobctlDriverThread's own total
|
|
* ~1550ms explicit sleep budget above to guarantee that). The actual
|
|
* pass/fail proof is entirely pipesink.c's own doing (its "payload-ok"
|
|
* needle only prints once it has verified, byte for byte, the exact
|
|
* payload pipesrc.c wrote through the real pipe) -- this thread's only job
|
|
* is to type the command and give the two spawned processes (neither of
|
|
* which ever sleeps or waits on a real timer, unlike alarmtest/polltest) a
|
|
* comfortable amount of real scheduled time to run to completion.
|
|
*/
|
|
static void pipelineDriverThread(void)
|
|
{
|
|
threadSleep(pipelineDriverInitialDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
kprintf("[kbd] typing shell command for the pipeline demo\n");
|
|
jobctlTypeFlushLine();
|
|
pipelineTypeCommand();
|
|
|
|
threadSleep(pipelineDriverSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/* Additional letters/symbols needed only by redirDriverThread's own three
|
|
commands below -- same cross-checked-against-scancodeLower sourcing as
|
|
every block above (index 0x20 is 'd', 0x11 is 'w', 0x32 is 'm', 0x2D is
|
|
'x', 0x34 is '.', 0x35 is '/', 0x1E is 'a', 0x33 is ',' -- '>' is
|
|
shift+'.' and '<' is shift+',' on a real US keyboard, scancodeUpper's
|
|
own mapping for those two indices, the same way scancodeMakeSeven is
|
|
shifted into '&' and scancodeMakeBackslash into '|' above). */
|
|
#define scancodeMakeD 0x20
|
|
#define scancodeMakeW 0x11
|
|
#define scancodeMakeM 0x32
|
|
#define scancodeMakeX 0x2D
|
|
#define scancodeMakeDot 0x34
|
|
#define scancodeMakeSlash 0x35
|
|
#define scancodeMakeA 0x1E
|
|
#define scancodeMakeComma 0x33
|
|
|
|
static void redirTypePath(void)
|
|
{
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeX);
|
|
injectKeyPress(scancodeMakeT);
|
|
}
|
|
|
|
static void redirTypeWriterName(void)
|
|
{
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeW);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeR);
|
|
}
|
|
|
|
static void redirTypeReaderName(void)
|
|
{
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeA);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeR);
|
|
}
|
|
|
|
static void redirTypeWriteTruncateCommand(void)
|
|
{
|
|
redirTypeWriterName();
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
redirTypePath();
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void redirTypeWriteAppendCommand(void)
|
|
{
|
|
redirTypeWriterName();
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
redirTypePath();
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void redirTypeReadCommand(void)
|
|
{
|
|
redirTypeReaderName();
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeComma);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
redirTypePath();
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
#define redirDriverInitialDelayMs 3600
|
|
#define redirDriverStepSettleMs 300
|
|
|
|
/*
|
|
* The live ring-3 proof for shell.c's new `cmd < IN` / `cmd > OUT` /
|
|
* `cmd >> OUT` file redirection (jobctlApplyRedirects/
|
|
* jobctlSpawnForegroundRedirected, jobctl.h): types three commands, one at
|
|
* a time, at the real interactive shell through the same injected-
|
|
* keystroke mechanism jobctlDriverThread/pipelineDriverThread above
|
|
* already use -- redirDriverInitialDelayMs gives pipelineDriverThread's
|
|
* own total ~3000ms budget (2500ms initial + 500ms settle) a comfortable
|
|
* margin before this thread ever touches the same shared keyboard buffer.
|
|
*
|
|
* Unlike pipelineDriverThread's single typed line, this is THREE separate
|
|
* commands, each one run to completion in the FOREGROUND (the shell
|
|
* genuinely blocks inside jobctlWaitForeground for each), so
|
|
* redirDriverStepSettleMs between them is not just cosmetic pacing --
|
|
* typing the next command's keystrokes while the shell is still mid-line
|
|
* on the previous one would queue them in the same 32-byte console-reader
|
|
* ring buffer (keyboard.c) that a real user's own typing shares, with no
|
|
* guarantee they would not overflow it or be echoed against the wrong
|
|
* still-open line. Each settle is comfortably longer than
|
|
* redirwriter/redirreader's own trivial one-shot work (a single write or
|
|
* read-to-EOF, neither ever sleeps or blocks on a timer) needs to spawn,
|
|
* run, exit, and be reaped, the same safety margin philosophy every
|
|
* other injected multi-step demo above already uses.
|
|
*
|
|
* "redirwriter > /tmp/redir.txt" (create/truncate) followed by
|
|
* "redirwriter >> /tmp/redir.txt" (append, same path) leaves a real tmpfs
|
|
* file holding two back-to-back copies of redirwriter.c's own payload;
|
|
* "redirreader < /tmp/redir.txt" then reads that file back through a
|
|
* REAL redirected stdin and compares it byte-for-byte against exactly
|
|
* that doubled payload. The actual pass/fail proof is entirely
|
|
* redirreader.c's own doing (its "payload-ok" needle only prints on an
|
|
* exact match) -- this thread's only job is to type the three commands
|
|
* and give the scheduler real time between them.
|
|
*/
|
|
static void redirDriverThread(void)
|
|
{
|
|
threadSleep(redirDriverInitialDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
kprintf("[kbd] typing shell commands for the file redirection demo\n");
|
|
jobctlTypeFlushLine();
|
|
|
|
redirTypeWriteTruncateCommand();
|
|
threadSleep(redirDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
redirTypeWriteAppendCommand();
|
|
threadSleep(redirDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
redirTypeReadCommand();
|
|
threadSleep(redirDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/* Digit needed only by stderrDriverThread's own three commands below --
|
|
same cross-checked-against-scancodeLower sourcing as every block above
|
|
(index 0x03 is '2', unshifted -- scancodeLower[3] == '2' in keyboard.c's
|
|
own table, no shift key involved unlike the '>'/'<' operators reused
|
|
from redirDriverThread's own scancodeMakeDot/scancodeMakeComma below). */
|
|
#define scancodeMakeTwo 0x03
|
|
|
|
/* "/tmp/err.txt" -- deliberately SHORTER than redirDriverThread's own
|
|
"/tmp/redir.txt" (12 vs 14 chars): "stderrwriter"/"stderrreader" are
|
|
already one character longer than "redirwriter"/"redirreader", and the
|
|
`2>`/`2>>` operators are one/two characters longer than `>`/`>>` -- with
|
|
the same path length as the file-redirection demo, the LONGEST typed
|
|
line here ("stderrwriter 2>> /tmp/err.txt\n", 30 bytes incl. newline)
|
|
would land right at keyboard.c's 32-byte per-thread console reader ring
|
|
buffer (consoleReaderBufferSize) with almost no margin left. A first
|
|
attempt at this demo used "/tmp/stderr.txt" (15 chars) and landed
|
|
EXACTLY at (and, for the `2>>` line, one byte OVER) that 32-byte
|
|
capacity -- ringPush (ring.c) silently drops a byte on a full buffer
|
|
with no retry, so the trailing '\n' itself was lost, and the shell's
|
|
line reader never saw a line terminator until the NEXT typed command's
|
|
bytes arrived, silently concatenating three separate typed commands
|
|
into one garbled line. Shortening the path restores the same few-bytes-
|
|
of-headroom margin redirDriverThread's own commands already prove works
|
|
(its longest line is 30 bytes incl. newline against the same 32-byte
|
|
capacity) instead of running right up against the boundary. */
|
|
static void stderrTypePath(void)
|
|
{
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeX);
|
|
injectKeyPress(scancodeMakeT);
|
|
}
|
|
|
|
static void stderrTypeWriterName(void)
|
|
{
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeW);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeR);
|
|
}
|
|
|
|
static void stderrTypeReaderName(void)
|
|
{
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeA);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeR);
|
|
}
|
|
|
|
static void stderrTypeWriteTruncateCommand(void)
|
|
{
|
|
stderrTypeWriterName();
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeTwo);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
stderrTypePath();
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void stderrTypeWriteAppendCommand(void)
|
|
{
|
|
stderrTypeWriterName();
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeTwo);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
stderrTypePath();
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void stderrTypeReadCommand(void)
|
|
{
|
|
stderrTypeReaderName();
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeComma);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
stderrTypePath();
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
#define stderrDriverInitialDelayMs 5500
|
|
#define stderrDriverStepSettleMs 300
|
|
|
|
/* Letter/symbol needed only by hereDriverThread's own command below -- same
|
|
cross-checked-against-scancodeLower sourcing as every block above (index
|
|
0x23 is 'h', 0x0C is '-', both unshifted -- keyboard.c's own
|
|
scancodeLower[0x23] == 'h' and scancodeLower[0x0C] == '-'). Every other
|
|
letter the command needs (e,r,s,t,o,k) is already defined by an earlier
|
|
block above. */
|
|
#define scancodeMakeH 0x23
|
|
#define scancodeMakeHyphen 0x0C
|
|
|
|
static void hereTypeCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeH);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeComma);
|
|
injectShiftedKeyPress(scancodeMakeComma);
|
|
injectShiftedKeyPress(scancodeMakeComma);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeH);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeHyphen);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeK);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
#define hereDriverInitialDelayMs 7500
|
|
#define hereDriverSettleMs 300
|
|
|
|
/*
|
|
* The live ring-3 proof for shell.c's new `cmd <<< TEXT` here-string syntax
|
|
* (jobctl.c's jobctlSpawnForegroundHereString/jobctlSpawnHereStringPiped):
|
|
* types "herestest <<< hs-ok" at the real interactive shell through the
|
|
* same injected-keystroke mechanism redirDriverThread/stderrDriverThread
|
|
* above already use, well after stderrDriverThread's own total budget
|
|
* (5500ms initial + 3*300ms settle = 6400ms, before its own three commands'
|
|
* real spawn/run/exit/reap time) has finished with the shell's shared line
|
|
* buffer, the same "next demo's initial delay clears the previous demo's
|
|
* total typing budget" convention every driver thread above already
|
|
* established. Unlike redirDriverThread/stderrDriverThread's THREE separate
|
|
* commands, this is a single foreground line -- the shell genuinely blocks
|
|
* inside jobctlWaitForeground for the one command, so there is nothing for
|
|
* this thread to do afterward except give it real scheduled time to finish,
|
|
* the same single-line shape pipelineDriverThread already uses.
|
|
*
|
|
* herestest.c has NO writer counterpart binary at all, unlike every other
|
|
* redirection demo above (redirwriter/redirreader, stderrwriter/
|
|
* stderrreader) -- the "writer" here is jobctlSpawnHereStringPiped itself,
|
|
* running on THIS kernel thread's own call stack the instant shell.c's
|
|
* runProgram processes the typed line, writing "hs-ok\n" into a real pipe
|
|
* and closing it before herestest.c ever gets a timeslice to read fd 0. The
|
|
* actual pass/fail proof is entirely herestest.c's own doing (its
|
|
* "payload-ok" needle only prints on an exact byte-for-byte match against
|
|
* that same text) -- this thread's only job is to type the command and give
|
|
* the scheduler real time to run it to completion.
|
|
*/
|
|
static void hereDriverThread(void)
|
|
{
|
|
threadSleep(hereDriverInitialDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
kprintf("[kbd] typing shell command for the here-string demo\n");
|
|
jobctlTypeFlushLine();
|
|
hereTypeCommand();
|
|
|
|
threadSleep(hereDriverSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/* Every letter/symbol "hcat <<< hpipe-ok | hsink" needs (h,c,a,t,<,p,i,e,-,
|
|
o,k,|,s,n) was already defined by an earlier block above -- see
|
|
pipelineTypeCommand's own '|' (scancodeMakeBackslash, shifted),
|
|
hereTypeCommand's own '<<<' (scancodeMakeComma, shifted) and '-'
|
|
(scancodeMakeHyphen, unshifted), and every plain letter from either one
|
|
or stderrTypeReadCommand/redirTypeReadCommand's own blocks. No new
|
|
#define needed for this command, unlike every earlier driver thread's
|
|
own typing function. */
|
|
static void hereStringPipelineTypeCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeH);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeA);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeComma);
|
|
injectShiftedKeyPress(scancodeMakeComma);
|
|
injectShiftedKeyPress(scancodeMakeComma);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeH);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeHyphen);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeK);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeBackslash);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeH);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeN);
|
|
injectKeyPress(scancodeMakeK);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
/* 9000ms, the same "clear the previous demo's own typing budget with a
|
|
comfortable margin" convention every earlier driver thread's own initial
|
|
delay already uses (hereDriverThread's own total budget is
|
|
7500+300=7800ms). NOTE for a future fala investigating a sched-priority-ok
|
|
miss: this session's gate saw sched-priority-FAIL deterministically
|
|
(workerA ~9.2x workerB, over the [1.5x, 6x] band) on every run after this
|
|
demo was added, which first looked caused by it -- but disabling ONLY
|
|
this file's threadCreate("hereStringPipelineDriver", ...) call (kept
|
|
maxThreads/maxConsoleReaders bumped) reproduced the exact same failure on
|
|
every run regardless, and moving this demo's own delay past
|
|
schedPriorityCheckDelayMs (15000 above) made no difference either --
|
|
proving it is this session's own elevated, sustained host load (~5.4-6.2,
|
|
well above the documented ~4.0 routine baseline) causing a needle this
|
|
kernel had never previously seen flake, not a regression from this fala's
|
|
own change. Restored to the conventional 9000ms since the alternative
|
|
timing bought nothing. */
|
|
#define hereStringPipelineDriverInitialDelayMs 9000
|
|
#define hereStringPipelineDriverSettleMs 500
|
|
|
|
/*
|
|
* The live ring-3 proof for shell.c's new `cmd1 <<< TEXT | cmd2` here-
|
|
* string-into-pipeline syntax (jobctl.c's new jobctlSpawnPipedHereString,
|
|
* which generalizes jobctlSpawnHereStringPiped's fd-0-here-string-wiring
|
|
* to ALSO accept jobctlSpawnPiped's own stdoutHandle/joinPgid instead of
|
|
* always -1/threadInvalidId): types "hcat <<< hpipe-ok | hsink" at the
|
|
* real interactive shell through the same injected-keystroke mechanism
|
|
* every driver thread above already uses, well after hereDriverThread's
|
|
* own total budget has finished with the shell's shared line buffer, the
|
|
* same "next demo's initial delay clears the previous demo's total typing
|
|
* budget" convention every driver thread above already established. Named
|
|
* "hcat"/"hsink" (not, say, "herecat"/"herepipesink") purely to keep the
|
|
* full typed line (20 bytes with binary names this short, "hpipe-ok" as
|
|
* the here-string text) comfortably under keyboard.c's 32-byte
|
|
* consoleReaderBufferSize per line -- see kmain.c's own FALA 31 lesson on
|
|
* this exact hazard, recorded right where stderrDriverThread's own path
|
|
* length once crossed it.
|
|
*
|
|
* hcat.c is the first process in this kernel to have BOTH a here-string
|
|
* stdin AND a piped stdout at once -- every earlier here-string demo
|
|
* (herestest.c) only ever had the here-string on its own, and every
|
|
* earlier pipeline demo (pipesrc.c/pipesink.c) only ever had plain piped
|
|
* fds, never a here-string. The actual pass/fail proof is entirely
|
|
* hsink.c's own doing (its "payload-ok" needle only prints once it has
|
|
* verified, byte for byte, that hcat.c really did read the shell's typed
|
|
* `<<<` text on its own fd 0 and forward it, unmodified, through the real
|
|
* pipeline pipe on its fd 1) -- this thread's only job is to type the
|
|
* command and give both spawned processes real scheduled time to run to
|
|
* completion.
|
|
*/
|
|
static void hereStringPipelineDriverThread(void)
|
|
{
|
|
threadSleep(hereStringPipelineDriverInitialDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
kprintf("[kbd] typing shell command for the here-string pipeline demo\n");
|
|
jobctlTypeFlushLine();
|
|
hereStringPipelineTypeCommand();
|
|
|
|
threadSleep(hereStringPipelineDriverSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/*
|
|
* The live ring-3 proof for shell.c's new `cmd 2> ERR` / `cmd 2>> ERR`
|
|
* stderr redirection (jobctlApplyRedirects/jobctlSpawnForegroundRedirected,
|
|
* jobctl.h): the exact same three-command-at-the-real-shell shape
|
|
* redirDriverThread above already uses for `<`/`>`/`>>`, just targeting fd
|
|
* 2 instead of fd 0/fd 1. stderrDriverInitialDelayMs gives redirDriverThread's
|
|
* own total budget (3600ms initial + 3*300ms settle = 4500ms, before its
|
|
* own three commands' real spawn/run/exit/reap time) a comfortable margin
|
|
* before this thread ever touches the same shared keyboard buffer, the
|
|
* same "next demo's initial delay clears the previous demo's total typing
|
|
* budget" convention pipelineDriverThread/redirDriverThread's own doc
|
|
* comments already established.
|
|
*
|
|
* "stderrwriter 2> /tmp/stderr.txt" (create/truncate) followed by
|
|
* "stderrwriter 2>> /tmp/stderr.txt" (append, same path) leaves a real
|
|
* tmpfs file holding two back-to-back copies of stderrwriter.c's own
|
|
* payload; "stderrreader < /tmp/stderr.txt" then reads that file back
|
|
* through a REAL redirected stdin and compares it byte-for-byte against
|
|
* exactly that doubled payload. The actual pass/fail proof is entirely
|
|
* stderrreader.c's own doing (its "payload-ok" needle only prints on an
|
|
* exact match) -- this thread's only job is to type the three commands and
|
|
* give the scheduler real time between them.
|
|
*/
|
|
static void stderrDriverThread(void)
|
|
{
|
|
threadSleep(stderrDriverInitialDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
kprintf("[kbd] typing shell commands for the stderr redirection demo\n");
|
|
jobctlTypeFlushLine();
|
|
|
|
stderrTypeWriteTruncateCommand();
|
|
threadSleep(stderrDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
stderrTypeWriteAppendCommand();
|
|
threadSleep(stderrDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
stderrTypeReadCommand();
|
|
threadSleep(stderrDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/* 10700ms gives hereStringPipelineDriverThread's own total budget
|
|
(9000+500=9500ms, above its own real hcat/hsink spawn/run/reap time) a
|
|
comfortable margin before this thread ever touches the same shared
|
|
keyboard buffer, the same "next demo's initial delay clears the previous
|
|
demo's total typing budget" convention every driver thread above already
|
|
established. Settle is 600ms (slightly more than pipelineDriverThread's
|
|
own 500ms for a 2-stage pipe) since this demo's pipe has one more real
|
|
process (pipesrc, pipecat, AND pipesink) to spawn, run, and reap before
|
|
the final needle can print. */
|
|
#define threeStagePipelineDriverInitialDelayMs 10700
|
|
#define threeStagePipelineDriverSettleMs 600
|
|
|
|
static void threeStagePipelineTypeCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeBackslash);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeA);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeBackslash);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeN);
|
|
injectKeyPress(scancodeMakeK);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
/*
|
|
* The live ring-3 proof for shell.c's new REAL N-stage pipeline
|
|
* (runPipelineStages/jobctlWaitPipelineStages) going past two stages: types
|
|
* "pipesrc | pipecat | pipesink" at the real interactive shell through the
|
|
* same injected-keystroke mechanism every driver thread above already
|
|
* uses, well after hereStringPipelineDriverThread's own total budget has
|
|
* finished with the shell's shared line buffer.
|
|
*
|
|
* pipesrc.c and pipesink.c are reused completely unchanged from the
|
|
* two-stage pipeline demo (FALA 29) -- their own narration ("wrote
|
|
* payload", "blocking on read, waiting for pipesrc", "payload-ok") is still
|
|
* literally true here, just relayed through one more real process instead
|
|
* of being directly adjacent. The new thing this demo actually proves is
|
|
* user/pipecat.c in the middle: jobctlSpawnPiped bakes a real pipe into
|
|
* BOTH its fd 0 (from pipesrc's pipe) and its fd 1 (to pipesink's pipe)
|
|
* before it ever runs a single instruction -- the first process in this
|
|
* kernel with two DIFFERENT real inter-process pipes on its stdio at once
|
|
* (hcat.c's own simultaneous piped-stdin-and-stdout, FALA 34, had a
|
|
* shell-fed here-string on the stdin side, never a second real pipe). The
|
|
* actual pass/fail proof is still entirely pipesink.c's own doing (its
|
|
* "payload-ok" needle only prints on an exact byte-for-byte match of
|
|
* whatever pipesrc originally wrote) -- this thread's only job is to type
|
|
* the command and give all three spawned processes (none of which ever
|
|
* sleeps or waits on a real timer) a comfortable amount of real scheduled
|
|
* time to run to completion.
|
|
*/
|
|
static void threeStagePipelineDriverThread(void)
|
|
{
|
|
threadSleep(threeStagePipelineDriverInitialDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
kprintf("[kbd] typing shell command for the three-stage pipeline demo\n");
|
|
jobctlTypeFlushLine();
|
|
threeStagePipelineTypeCommand();
|
|
|
|
threadSleep(threeStagePipelineDriverSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/* '=' (sysGetEnv/sysSetEnv's own operator, shell.c's `export N=V`
|
|
builtin), 'y'/'Y', and 'v'/'V' are the only three make codes no earlier
|
|
driver thread above ever needed -- every other letter "export",
|
|
"GREETING", "envprint", and "env" use (e,x,p,o,r,t,g,i,n,f,s,b) was
|
|
already defined by an earlier block. Cross-checked against
|
|
keyboard.c's own scancodeLower/scancodeUpper tables the same way every
|
|
block above documents for its own new codes. */
|
|
#define scancodeMakeEquals 0x0D
|
|
#define scancodeMakeY 0x15
|
|
#define scancodeMakeV 0x2F
|
|
|
|
static void envTypeExportCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeX);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeG);
|
|
injectShiftedKeyPress(scancodeMakeR);
|
|
injectShiftedKeyPress(scancodeMakeE);
|
|
injectShiftedKeyPress(scancodeMakeE);
|
|
injectShiftedKeyPress(scancodeMakeT);
|
|
injectShiftedKeyPress(scancodeMakeI);
|
|
injectShiftedKeyPress(scancodeMakeN);
|
|
injectShiftedKeyPress(scancodeMakeG);
|
|
injectKeyPress(scancodeMakeEquals);
|
|
injectKeyPress(scancodeMakeF);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeB);
|
|
injectKeyPress(scancodeMakeY);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void envTypeEnvprintCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeN);
|
|
injectKeyPress(scancodeMakeV);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeN);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void envTypeEnvCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeN);
|
|
injectKeyPress(scancodeMakeV);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
/* 12500ms gives threeStagePipelineDriverThread's own total budget
|
|
(10700+600=11300ms, above its own real 3-process pipeline run time) a
|
|
comfortable margin before this thread ever touches the same shared
|
|
keyboard buffer, the same "next demo's initial delay clears the
|
|
previous demo's total budget" convention every driver thread above
|
|
already established. Settle is 400ms per step -- envprint (unlike most
|
|
earlier demos) never blocks on anything (no pipe, no fifo, no signal),
|
|
it just runs two syscalls and prints, so this is comfortable headroom
|
|
rather than a tightly reasoned minimum. */
|
|
#define envDriverInitialDelayMs 12500
|
|
#define envDriverStepSettleMs 400
|
|
|
|
/*
|
|
* Live ring-3 proof for the new real environment-variable subsystem
|
|
* (sched.h's Thread.env, syscall.c's sysGetEnv/sysSetEnv, shell.c's new
|
|
* `export`/`env` builtins and `$NAME` expansion): types "export
|
|
* GREETING=frostbyte" at the real interactive shell (sets it in the
|
|
* SHELL's own Thread.env, currentThreadSetEnv), then "envprint" (loaded
|
|
* on demand, /bin/envprint -- proves the freshly spawned child inherited
|
|
* a real copy via initialEnv and that its own later userSetEnv is visible
|
|
* to itself), then a final bare "env" (shell.c's listing builtin) whose
|
|
* output is the actual proof of isolation: if envprint.c's own
|
|
* userSetEnv("GREETING", "child-mutated") had somehow reached back into
|
|
* the PARENT shell's table (a shared-pointer bug, not the real
|
|
* independent-copy semantics initialEnv gives), this "env" would print
|
|
* "child-mutated" instead of the original "frostbyte" -- the same
|
|
* "child's own later mutation is proven not to leak back" shape
|
|
* cwdtest.c's post-userWait getcwd() check already established for
|
|
* per-process cwd. The actual pass/fail proof for the first two commands
|
|
* is entirely envprint.c's own doing; this thread's only job is to type
|
|
* the three commands and give the scheduler real time between them.
|
|
*/
|
|
static void envDriverThread(void)
|
|
{
|
|
threadSleep(envDriverInitialDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
kprintf("[kbd] typing shell commands for the environment variable demo\n");
|
|
jobctlTypeFlushLine();
|
|
|
|
envTypeExportCommand();
|
|
threadSleep(envDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
envTypeEnvprintCommand();
|
|
threadSleep(envDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
envTypeEnvCommand();
|
|
threadSleep(envDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/* '*' is shift+8 on a real US keyboard -- keyboard.c's scancodeUpper table
|
|
maps index scancodeMakeEight (the same physical key as scancodeLower's
|
|
plain '8') to '*' only while shiftHeld is true, the same convention
|
|
scancodeMakeDot/scancodeMakeComma above already use for '>'/'<'. No
|
|
other new letter is needed for this demo's five commands -- every letter
|
|
"mkdir"/"echo"/"globcat"/"tmp"/"gd"/"txt"/"log" needs (m,k,d,i,r,e,c,h,o,
|
|
g,l,b,a,t,p,x,s) was already defined by an earlier block. */
|
|
#define scancodeMakeEight 0x09
|
|
|
|
static void globTypeMkdirCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeK);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeR);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeG);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void globTypeEchoACommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeH);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeA);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeG);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeA);
|
|
injectKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeX);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void globTypeEchoBCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeH);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeB);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeG);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeB);
|
|
injectKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeX);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
/* Writes /tmp/gd/c.log (NOT .txt) -- the sibling file globTypeGlobcatCommand
|
|
below's own "*.txt" pattern must NOT match, proving shellExpandGlobs
|
|
really filters by pattern instead of just listing the whole directory. */
|
|
static void globTypeEchoSkipCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeH);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectShiftedKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeG);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeL);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeG);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
static void globTypeGlobcatCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeG);
|
|
injectKeyPress(scancodeMakeL);
|
|
injectKeyPress(scancodeMakeO);
|
|
injectKeyPress(scancodeMakeB);
|
|
injectKeyPress(scancodeCPressMake);
|
|
injectKeyPress(scancodeMakeA);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeP);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeG);
|
|
injectKeyPress(scancodeMakeD);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectShiftedKeyPress(scancodeMakeEight);
|
|
injectKeyPress(scancodeMakeDot);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeX);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
/* 15000ms clears envDriverThread's own total budget (12500 initial +
|
|
3*400 settle = 13700ms, above its own real spawn/run/reap time), the same
|
|
"next demo's initial delay clears the previous demo's total budget"
|
|
convention every driver thread above already establishes. Settle is
|
|
300ms per step, matching redirDriverThread's own choice for similarly
|
|
trivial commands (a single mkdir or a single short file write, none of
|
|
which ever sleeps or blocks on a timer) -- comfortable headroom to let
|
|
the shell finish running each FOREGROUND command before the next one's
|
|
keystrokes are typed into the same shared 32-byte console-reader ring
|
|
buffer (keyboard.c). */
|
|
#define globDriverInitialDelayMs 15000
|
|
#define globDriverStepSettleMs 300
|
|
|
|
/*
|
|
* Live ring-3 proof for shell.c's new wildcard glob expansion
|
|
* (shellExpandGlobs, shellGlobMatch/shellTokenHasGlob/shellSplitGlobToken):
|
|
* types four setup commands at the real interactive shell (mkdir, then
|
|
* three "echo X > /tmp/gd/NAME" writes -- two matching *.txt, one
|
|
* deliberately a *.log that must NOT match), then "globcat /tmp/gd/ *.txt".
|
|
* shell.c resolves that last argv token against the real tmpfs directory
|
|
* /tmp/gd (vfsChild/vfsNext, the exact traversal the `ls` builtin already
|
|
* uses) and replaces it with however many real paths matched BEFORE
|
|
* globcat.c (loaded on demand, like envprint/hcat/pipecat above) ever runs
|
|
* a single instruction -- so globcat.c's own argc/argv and each file's
|
|
* content are the actual proof: exactly 2 files (never 3 -- c.log must be
|
|
* filtered out by pattern, not just "everything in the directory"), each
|
|
* one's real resolved path and its own distinct content. The actual
|
|
* pass/fail proof is entirely globcat.c's own doing; this thread's only job
|
|
* is to type the five commands and give the scheduler real time between
|
|
* them.
|
|
*/
|
|
static void globDriverThread(void)
|
|
{
|
|
threadSleep(globDriverInitialDelayMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
kprintf("[kbd] typing shell commands for the wildcard glob demo\n");
|
|
jobctlTypeFlushLine();
|
|
|
|
globTypeMkdirCommand();
|
|
threadSleep(globDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
globTypeEchoACommand();
|
|
threadSleep(globDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
globTypeEchoBCommand();
|
|
threadSleep(globDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
globTypeEchoSkipCommand();
|
|
threadSleep(globDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
globTypeGlobcatCommand();
|
|
threadSleep(globDriverStepSettleMs);
|
|
while (currentThread()->state == threadSleeping)
|
|
halt();
|
|
|
|
for (;;)
|
|
halt();
|
|
}
|
|
|
|
/* 19500ms clears globDriverThread's own total budget (15000 initial +
|
|
5*300 settle = 16500ms, plus the real time globcat.c itself needs to
|
|
spawn/run/read two files/print/exit after that last keystroke) -- the
|
|
same "next demo's initial delay clears the previous demo's total budget
|
|
with margin" convention every driver thread above establishes. A single
|
|
settle step is enough -- see the file comment on lsDriverThread below
|
|
for why this demo types only ONE command, unlike every prior driver
|
|
thread's multi-command setup sequence: tools/ci-test.sh's QEMU timeout
|
|
is a fixed 25 REAL (host wall-clock) seconds shared by the entire boot,
|
|
and this is already the last of well over a dozen shell-driven demos
|
|
stacked at increasing absolute timestamps in that same fixed window, so
|
|
minimizing this demo's own keystroke count directly buys back margin
|
|
for the whole chain instead of spending it. */
|
|
#define lsDriverInitialDelayMs 19500
|
|
#define lsDriverStepSettleMs 300
|
|
|
|
static void lsTypeLongListCommand(void)
|
|
{
|
|
injectKeyPress(scancodeMakeL);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeHyphen);
|
|
injectKeyPress(scancodeMakeL);
|
|
injectKeyPress(scancodeMakeSpace);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeN);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeSlash);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeI);
|
|
injectKeyPress(scancodeMakeM);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeE);
|
|
injectKeyPress(scancodeMakeS);
|
|
injectKeyPress(scancodeMakeT);
|
|
injectKeyPress(scancodeMakeEnter);
|
|
}
|
|
|
|
/*
|
|
* Live proof for shell.c's new generalized `ls`/`ls -l` (listDir/
|
|
* printLsEntry): types `ls -l /mnt/timetest` at the real interactive
|
|
* shell. `/mnt/timetest` is deliberately NOT created by this demo --
|
|
* timetest.c (FALA 39, boot-time synchronous, long finished by the time
|
|
* any shell-driven demo runs) already left it behind with two real files
|
|
* (data.txt, and alias.txt, a hardlink sharing the same inode/mtime) once
|
|
* `timetest: PASS` prints, so this demo needs exactly ONE typed command
|
|
* instead of the usual "mkdir + write setup, then the command under test"
|
|
* shape every earlier shell demo used -- both a deliberate budget saving
|
|
* (see lsDriverInitialDelayMs's own comment) and, incidentally, a second
|
|
* independent proof that `ls -l`'s real stat data (size, mtime) matches
|
|
* what timetest.c's own syscall-level userStat already established for
|
|
* the exact same on-disk inode, through a completely different code path
|
|
* (the interactive shell builtin instead of a ring-3 program's syscalls).
|
|
* `/mnt`, not `/tmp`, is the whole point -- the OLD `ls` only ever
|
|
* understood the tmpfs VfsNode tree via vfsLookup/vfsChild/vfsNext, so any
|
|
* `/mnt` directory silently reported "no such path" no matter what it
|
|
* actually held (fstest.c/linktest.c could only ever prove SFS directory
|
|
* listing through a raw userGetdents syscall, never through this
|
|
* builtin). The needles tools/ci-test.sh checks for are the deterministic
|
|
* type+size+name prefix of each `-l` output line ("f <N> data.txt " and
|
|
* "f <N> alias.txt ", both files' real on-disk size) plus a "20" year-
|
|
* prefix right after one of them, proving a real mtime got formatted and
|
|
* printed (sfs.h's on-disk timestamp, rtc.c's new rtcCivilFromEpoch)
|
|
* 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)
|
|
{
|
|
consoleInit();
|
|
banner();
|
|
|
|
if (magic == multibootBootMagic)
|
|
kprintf("[boot] multiboot2 magic verified (%x)\n", magic);
|
|
else
|
|
kprintf("[boot] unexpected boot magic %x\n", magic);
|
|
|
|
gdtInit();
|
|
kprintf("[gdt] kernel + user segments, tss slot reserved\n");
|
|
|
|
idtInit();
|
|
kprintf("[idt] 256 vectors (32 exceptions, 16 irqs, int 0x80 syscall)\n");
|
|
|
|
picRemap();
|
|
for (uint8 irq = 0; irq < 16; irq++)
|
|
picSetMask(irq);
|
|
picClearMask(0);
|
|
picClearMask(1);
|
|
kprintf("[pic] remapped to 0x20-0x2f, irq0 and irq1 unmasked\n");
|
|
|
|
uint64 moduleStart = 0;
|
|
uint64 moduleEnd = 0;
|
|
bool haveModule = multibootFindModule(multibootInfo, &moduleStart, &moduleEnd);
|
|
|
|
pmmInit(multibootInfo, moduleEnd);
|
|
kprintf("[pmm] %lu KiB usable, %lu frames, %lu free after kernel\n",
|
|
pmmTotalBytes() / 1024, pmmTotalFrames(), pmmFreeFrames());
|
|
|
|
uint64 heapStart = pmmReservedEnd();
|
|
pmmReserve(heapStart, heapSize);
|
|
heapInit(heapStart, heapSize);
|
|
kprintf("[heap] free-list allocator at %p, %u KiB\n", (void*)heapStart, heapSize / 1024);
|
|
|
|
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);
|
|
|
|
keyboardInit();
|
|
kprintf("[kbd] ps/2 keyboard driver ready\n");
|
|
|
|
schedInit();
|
|
tssInit((uint64)(bootstrapKernelStack + sizeof(bootstrapKernelStack)));
|
|
kprintf("[tss] loaded, rsp0 bootstrap stack installed\n");
|
|
|
|
vfsInit();
|
|
if (haveModule)
|
|
vfsMountUstar(moduleStart, moduleEnd);
|
|
else
|
|
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);
|
|
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);
|
|
spawnUser("/bin/counter", "counter", 0x58000000, 0);
|
|
spawnUser("/bin/sbrktest", "sbrktest", 0x68000000, 0x64000000);
|
|
spawnUser("/bin/fileread", "fileread", 0x78000000, 0);
|
|
spawnUser("/bin/pipetest", "pipetest", 0x88000000, 0);
|
|
spawnUser("/bin/disktest", "disktest", 0x98000000, 0);
|
|
spawnUser("/bin/fstest", "fstest", 0xA8000000, 0);
|
|
spawnUser("/bin/linktest", "linktest", 0xB8000000, 0);
|
|
spawnUser("/bin/fifomaster", "fifomaster", 0x118000000, 0);
|
|
spawnUser("/bin/killtest", "killtest", 0x148000000, 0);
|
|
spawnUser("/bin/namedreader", "namedreader", 0x168000000, 0);
|
|
spawnUser("/bin/namedwriter", "namedwriter", 0x178000000, 0);
|
|
spawnUser("/bin/consolereader1", "consolereader1", 0x188000000, 0);
|
|
spawnUser("/bin/consolereader2", "consolereader2", 0x198000000, 0);
|
|
threadCreate("consoleInjector", consoleInjectorThread);
|
|
spawnUser("/bin/sigtest", "sigtest", 0x1A8000000, 0);
|
|
spawnUser("/bin/jobtest", "jobtest", 0x1D8000000, 0);
|
|
spawnUser("/bin/nesttest", "nesttest", 0x1F8000000, 0);
|
|
spawnUser("/bin/eintrtest", "eintrtest", 0x218000000, 0);
|
|
spawnUser("/bin/restarttest", "restarttest", 0x238000000, 0);
|
|
spawnUser("/bin/masktest", "masktest", 0x258000000, 0);
|
|
spawnUser("/bin/sigchldtest", "sigchldtest", 0x278000000, 0);
|
|
spawnUser("/bin/waitanytest", "waitanytest", 0x298000000, 0);
|
|
spawnUser("/bin/stopchldtest", "stopchldtest", 0x2C8000000, 0);
|
|
spawnUser("/bin/nodefertest", "nodefertest", 0x2E8000000, 0);
|
|
spawnUser("/bin/alarmtest", "alarmtest", 0x2F8000000, 0);
|
|
spawnUser("/bin/siginfotest", "siginfotest", 0x318000000, 0);
|
|
spawnUser("/bin/selfstoptest", "selfstoptest", 0x3F8000000, 0);
|
|
spawnUser("/bin/cwdtest", "cwdtest", 0x458000000, 0);
|
|
spawnUser("/bin/nicetest", "nicetest", 0x4A8000000, 0);
|
|
spawnUser("/bin/date", "date", 0x4B8000000, 0);
|
|
spawnUser("/bin/timetest", "timetest", 0x4D8000000, 0);
|
|
spawnUser("/bin/rmtest", "rmtest", 0x508000000, 0);
|
|
spawnUser("/bin/permtest", "permtest", 0x518000000, 0);
|
|
spawnUser("/bin/identtest", "identtest", 0x528000000, 0);
|
|
spawnUser("/bin/dirpermtest", "dirpermtest", 0x538000000, 0);
|
|
spawnUser("/bin/grouptest", "grouptest", 0x548000000, 0);
|
|
spawnUser("/bin/pidfdtest", "pidfdtest", 0x558000000, 0);
|
|
/* mmapprotvictim and mmapexecvictim are deliberately NOT spawned here
|
|
-- like killvictim (killtest's own runtime-spawned target, elsewhere
|
|
in this function), both are only ever loaded at runtime, by
|
|
mmaptest itself (userSpawn), never directly by kmain.c. */
|
|
spawnUser("/bin/mmaptest", "mmaptest", 0x568000000, 0);
|
|
spawnUser("/bin/polltest", "polltest", 0x378000000, 0);
|
|
pgroupfgPid = spawnUser("/bin/pgroupfg", "pgroupfg", 0x338000000, 0);
|
|
pgroupbgPid = spawnUser("/bin/pgroupbg", "pgroupbg", 0x348000000, 0);
|
|
if (pgroupfgPid != threadInvalidId) {
|
|
/* Real shell/tcsetpgrp behavior: whichever job was just launched in
|
|
the foreground claims the terminal explicitly -- pgroupbg is
|
|
deliberately left alone (its own default group, never named
|
|
here), the same way a background job never touches the
|
|
controlling terminal at all. */
|
|
keyboardSetForegroundPgid(pgroupfgPid);
|
|
}
|
|
if (pgroupfgPid != threadInvalidId && pgroupbgPid != threadInvalidId)
|
|
threadCreate("pgroupInjector", pgroupInjectorThread);
|
|
/* jobctlfg/jobctlbg are deliberately NOT spawned here -- unlike every
|
|
program above, they are only ever loaded on demand, at runtime, by
|
|
the real interactive shell (shellRun, threadCreate'd near the top of
|
|
this function) once jobctlDriverThread types their names at it. */
|
|
threadCreate("jobctlDriver", jobctlDriverThread);
|
|
/* pipesrc/pipesink are, like jobctlfg/jobctlbg just above, deliberately
|
|
NOT spawned here -- loaded on demand by the real shell once
|
|
pipelineDriverThread types "pipesrc | pipesink" at it. */
|
|
threadCreate("pipelineDriver", pipelineDriverThread);
|
|
/* redirwriter/redirreader are, like pipesrc/pipesink just above,
|
|
deliberately NOT spawned here -- loaded on demand by the real shell
|
|
once redirDriverThread types the redirected commands at it. */
|
|
threadCreate("redirDriver", redirDriverThread);
|
|
/* stderrwriter/stderrreader are, like redirwriter/redirreader just
|
|
above, deliberately NOT spawned here -- loaded on demand by the real
|
|
shell once stderrDriverThread types the stderr-redirected commands
|
|
at it. */
|
|
threadCreate("stderrDriver", stderrDriverThread);
|
|
/* herestest is, like stderrwriter/stderrreader just above, deliberately
|
|
NOT spawned here -- loaded on demand by the real shell once
|
|
hereDriverThread types the here-string command at it. */
|
|
threadCreate("hereDriver", hereDriverThread);
|
|
/* hcat/hsink are, like herestest just above, deliberately NOT spawned
|
|
here -- loaded on demand by the real shell once
|
|
hereStringPipelineDriverThread types the here-string-into-pipeline
|
|
command at it. */
|
|
threadCreate("hereStringPipelineDriver", hereStringPipelineDriverThread);
|
|
/* pipecat is, like hcat/hsink just above, deliberately NOT spawned here
|
|
-- loaded on demand by the real shell once
|
|
threeStagePipelineDriverThread types the three-stage pipeline
|
|
command at it (pipesrc/pipesink themselves are also on-demand-only,
|
|
unchanged since FALA 29). */
|
|
threadCreate("threeStagePipelineDriver", threeStagePipelineDriverThread);
|
|
/* envprint is, like pipecat just above, deliberately NOT spawned here
|
|
-- loaded on demand by the real shell once envDriverThread types
|
|
"export GREETING=frostbyte" then "envprint" at it. */
|
|
threadCreate("envDriver", envDriverThread);
|
|
/* globcat is, like envprint just above, deliberately NOT spawned here
|
|
-- loaded on demand by the real shell once globDriverThread types
|
|
"globcat /tmp/gd/ *.txt" at it (shell.c's new wildcard glob
|
|
expansion). */
|
|
threadCreate("globDriver", globDriverThread);
|
|
/* No new /bin binary here -- lsDriverThread only exercises shell.c's
|
|
existing mkdir/echo/ls builtins, never spawns anything. */
|
|
threadCreate("lsDriver", lsDriverThread);
|
|
/* false is, like globcat above, deliberately NOT spawned here --
|
|
loaded on demand by the real shell once andOrDriverThread types the
|
|
`&&`/`||`-chained command at it (shell.c's new conditional
|
|
chaining). */
|
|
threadCreate("andOrDriver", andOrDriverThread);
|
|
execDemoPid = spawnUser("/bin/proctest", "proctest", 0xF8000000, 0);
|
|
if (execDemoPid != threadInvalidId)
|
|
threadCreate("execReaper", execReaperThread);
|
|
|
|
kprintf("[sched] %u threads total (idle, shell, 2 workers, priority verifier, user programs)\n",
|
|
threadCount());
|
|
kprintf("\n[boot] enabling interrupts, starting preemptive scheduler\n\n");
|
|
|
|
schedStart();
|
|
}
|