From 5585b1196844c957ce071c438fc72b3740e32696 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 15:42:48 +0200 Subject: [PATCH] feat: add selfstoptarget and sigchldchild --- user/selfstoptarget.c | 72 +++++++++++++++++++++++++++++++++++++++++++ user/sigchldchild.c | 24 +++++++++++++++ user/sigdefault.c | 26 ++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 user/selfstoptarget.c create mode 100644 user/sigchldchild.c create mode 100644 user/sigdefault.c diff --git a/user/selfstoptarget.c b/user/selfstoptarget.c new file mode 100644 index 0000000..aaea931 --- /dev/null +++ b/user/selfstoptarget.c @@ -0,0 +1,72 @@ +#include "ulib.h" + +/* + * Only ever spawned by selfstoptest.c, with argv[1] = a shared pipe handle + * (userPipeCreate'd by selfstoptest.c, decToString'd exactly like every + * other *test / *target pair's own shared-handle argv) this process attaches + * as the WRITER end. + * + * Unlike jobtest.c/jobtarget.c (job control's other live demo), the SIGSTOP + * here targets THIS process's OWN pid -- self-directed job control + * (syscall.c's sysSigSend, the "selfTargeted" branch right after + * sigRaise/threadStop) rather than one process stopping a different one. + * No ready-pipe rendezvous is needed before the self-stop itself: unlike + * jobtest.c's own sigStop (sent from a DIFFERENT process, which could + * otherwise race ahead of jobtarget's own setup), this process is the only + * one that can ever call self-stop, so there is nothing else to order it + * against -- it simply self-stops the moment it has attached the pipe. + * + * selfstoptest.c's own real proof that this process is genuinely Stopped + * (not still running, not merely eventually preempted by the next timer + * tick) is entirely on ITS side (a real SIGCHLD siCodeChldStopped + * notification, then a non-blocking poll proving the pipe below is still + * empty) -- this process only needs to write its own proof byte AFTER + * actually resuming, which can only happen once selfstoptest.c's own + * userSigSend(this pid, sigCont) runs. + */ + +int main(int argc, char** argv) +{ + print("selfstoptarget: start, pid="); + printDec(userGetpid()); + print("\n"); + + if (argc != 2) { + print("selfstoptarget: FAIL expected one argv (proof pipe handle)\n"); + return 1; + } + + long handle = parseDec(argv[1]); + long proofFd = userPipeAttach(handle, 1); + if (proofFd < 0) { + print("selfstoptarget: FAIL attach proof pipe as writer\n"); + return 1; + } + + print("selfstoptarget: self-stopping now\n"); + + /* Self-targeted SIGSTOP: syscall.c's sysSigSend recognizes rawPid == + senderPid (this process's own pid) as "selfTargeted", and after + threadStop (called from sigRaise above) flips this thread's own + state to threadStopped, explicitly reschedules instead of just + falling through to its normal return-to-ring3 path -- the exact gap + syscall.c's own comment documents (a self-stopped thread that simply + returned to ring 3 would keep running, unnoticed, until the next + timer tick). Does not return until a real + userSigSend(this pid, sigCont) from selfstoptest.c moves this thread + back to threadRunnable and the scheduler actually picks it again. */ + if (userSigSend(userGetpid(), sigStop) != 0) { + print("selfstoptarget: FAIL self-directed sigStop rejected\n"); + return 1; + } + + print("selfstoptarget: resumed-after-self-stop-ok\n"); + + if (userWrite((int)proofFd, "p", 1) != 1) { + print("selfstoptarget: FAIL write proof byte\n"); + return 1; + } + userClose((int)proofFd); + + return 66; +} diff --git a/user/sigchldchild.c b/user/sigchldchild.c new file mode 100644 index 0000000..a5d3e8c --- /dev/null +++ b/user/sigchldchild.c @@ -0,0 +1,24 @@ +#include "ulib.h" + +/* + * Only ever spawned by sigchldtest.c. Does nothing rendezvous-shaped + * (unlike eintrtarget.c/masktarget.c's own targets) because it does not + * need to: sigchldtest.c's own proof (a real sigChld raised by the kernel + * against whoever spawned this process, sched.c's parentId/threadExit) + * does not depend on any particular ordering between this process's own + * death and sigchldtest.c noticing it -- sigRaise sets the pending bit on + * the parent unconditionally, whatever its state is at that instant, and + * sigchldtest.c's own bounded poll loop (the same "check a flag every + * userSleep(20)" idiom masktarget.c/nesttarget.c already use) picks it up + * on its very next natural wake either way. Just runs and exits with a + * distinctive code sigchldtest.c can verify through userWait after the + * fact. + */ +int main(void) +{ + print("sigchldchild: start, pid="); + printDec(userGetpid()); + print("\n"); + print("sigchldchild: exiting now\n"); + return 84; +} diff --git a/user/sigdefault.c b/user/sigdefault.c new file mode 100644 index 0000000..b18b878 --- /dev/null +++ b/user/sigdefault.c @@ -0,0 +1,26 @@ +#include "ulib.h" + +/* + * Only ever spawned by sigtest.c. Installs no handler at all, so the + * sigTerm sigtest.c sends it must run the OTHER branch of + * signal.c's sigConsumePending: default disposition, which + * isr.c's deliverPendingSignals turns into a real threadExit -- proving a + * signal with no handler actually terminates its target (exit code + * 128+signum, see isr.c), not just that a handler-based one can be caught. + * Reaching the FAIL print below would mean the default-terminate path + * never fired at all. + */ + +int main(void) +{ + print("sigdefault: start, pid="); + printDec(userGetpid()); + print("\n"); + print("sigdefault: waiting to be terminated by default disposition\n"); + + for (int i = 0; i < 40; i++) + userSleep(20); + + print("sigdefault: FAIL still alive after default-disposition signal\n"); + return 1; +}