feat: extend alarmdefault and argecho

This commit is contained in:
dualfroz committed 2026-09-08 21:28:10 +02:00
1 parent 4dee5e3009
commit 09239a27e8
5 files changed
+149

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
#include "ulib.h"
/*
* Only ever spawned by alarmtest.c. Installs NO sigAlrm handler at all --
* the real-Unix default disposition for SIGALRM (signal.c's
* sigDefaultIsIgnore explicitly does NOT list sigAlrm, so it falls through
* to the same default-Terminate path every other "real" signal in this
* kernel already has) is Terminate, not Ignore. userAlarm(1) schedules a
* real SIGALRM one second from now against THIS process; the read below
* blocks on a pipe no writer will ever attach to (the same "blocked
* forever" setup eintrtarget.c/killvictim.c already use), so the only way
* this process ever leaves that read is the alarm actually firing and
* killing it -- exactly like eintrtarget.c, except the interrupting signal
* here comes from schedule()'s own timer-tick expiry scan (sched.c), not
* from another process's userSigSend. If threadSetAlarm/schedule's expiry
* scan were ever broken, this process would simply never return from the
* read below, hanging the whole boot gate to its 25s QEMU timeout instead
* of merely failing a check -- the same fault-injection-visible shape
* eintrtest.c's own file comment already establishes for early-wake.
*/
int main(void)
{
print("alarmdefault: start, pid=");
printDec(userGetpid());
print("\n");
long victimHandle = userPipeCreate();
if (victimHandle < 0) {
print("alarmdefault: FAIL create victim pipe\n");
return 1;
}
long victimFd = userPipeAttach(victimHandle, 0);
if (victimFd < 0) {
print("alarmdefault: FAIL attach victim pipe\n");
return 1;
}
if (userAlarm(1) != 0) {
print("alarmdefault: FAIL unexpected pre-existing alarm\n");
return 1;
}
print("alarmdefault: blocking on read forever\n");
char buffer[4];
userRead((int)victimFd, buffer, sizeof(buffer));
/* Only reached if userRead somehow returned on its own -- nothing ever
writes to the victim pipe, so the alarm not firing (or firing but
not actually terminating this process) is the only way to get here. */
print("alarmdefault: FAIL read returned without being signaled\n");
return 1;
}
+24
View File
@@ -0,0 +1,24 @@
#include "ulib.h"
/*
* Real ring-3 proof that shell.c's `$NAME`/`$?` expansion (shellExpandArgv,
* shellExpandToken) genuinely reaches a SPAWNED program's own argv, not
* just a shell builtin's -- every earlier consumer of expansion (export,
* env, echo) is a builtin, and shell.c's doEcho never calls shellExpandArgv
* at all (only runProgram/runPipelineStages do, for a real spawned
* process), so nothing in this kernel's live-demo chain had ever actually
* typed a `$` at the real shell before this program existed. Prints each
* argv[1..argc) on its own line, prefixed like globcat.c's own
* "globcat: " diagnostic style -- a generic argv printer, not tied to any
* one variable, so a future demo can reuse it for any other `$NAME`/`$?`
* case without needing yet another single-purpose binary.
*/
int main(int argc, char** argv)
{
for (int i = 1; i < argc; i++) {
print("argecho: ");
print(argv[i]);
print("\n");
}
return 0;
}
+37
View File
@@ -0,0 +1,37 @@
#include "ulib.h"
/*
* Boot-spawned independently of consolereader2.c (see kmain.c's spawnUser
* calls) -- like namedreader.c/namedwriter.c, neither process's pid, argv,
* or fd table ever mentions the other. Both simply read fd 0, which every
* process gets for free (vfs.c's vfsFdInit) as its own console reader queue
* (keyboard.h) rather than a single kernel-wide buffer: proving that two
* completely unrelated processes reading "the keyboard" each get their own
* independent copy of every keystroke instead of racing each other for it.
*
* kmain.c's consoleInjectorThread fires exactly one synthetic keystroke a
* few hundred milliseconds after boot (there is no automated way to drive a
* real PS/2 device under headless QEMU, see its own comment) through the
* same keyboardHandleScancode the real IRQ handler calls -- so this process
* has to genuinely park in userRead across many timer ticks and be woken
* later, not just win a race against a byte that was already queued.
*/
int main(void)
{
print("consolereader1: start, pid=");
printDec(userGetpid());
print(", blocking on fd 0\n");
char c = 0;
long got = userRead(0, &c, 1);
if (got != 1) {
print("consolereader1: FAIL read\n");
return 1;
}
print("consolereader1: got byte '");
userWrite(1, &c, 1);
print("' -> console-fanout-1-ok\n");
return 0;
}
+30
View File
@@ -0,0 +1,30 @@
#include "ulib.h"
/* See consolereader1.c's comment -- an entirely independent process reading
the same fd 0, spawned separately by kmain.c, sharing nothing with
consolereader1 but the fact that both happen to be alive when
consoleInjectorThread's single synthetic keystroke fires. If fan-out
ever regressed back to one shared buffer, whichever of the two processes
the scheduler happened to run first would steal the only queued byte and
the other would hang forever -- the boot gate's timeout would catch that
as a missing needle rather than a hang, since only this process is
missing, not the whole machine. */
int main(void)
{
print("consolereader2: start, pid=");
printDec(userGetpid());
print(", blocking on fd 0\n");
char c = 0;
long got = userRead(0, &c, 1);
if (got != 1) {
print("consolereader2: FAIL read\n");
return 1;
}
print("consolereader2: got byte '");
userWrite(1, &c, 1);
print("' -> console-fanout-2-ok\n");
return 0;
}
+5
View File
@@ -2,6 +2,11 @@
.global _start
_start:
xorq %rbp, %rbp
/* Real x86_64 SysV process entry: rsp points at argc, with argv[0..]
and a NULL terminator right above it (elfBuildInitialStack,
elf.c/syscall.c doExec/doSpawn, kmain.c spawnUser). */
movq (%rsp), %rdi
leaq 8(%rsp), %rsi
call main
movq %rax, %rdi
movq $0, %rax