diff --git a/user/nesttest.c b/user/nesttest.c new file mode 100644 index 0000000..2920029 --- /dev/null +++ b/user/nesttest.c @@ -0,0 +1,112 @@ +#include "ulib.h" + +/* + * Boot-time driver for real, ring-3 NESTED signal delivery (signal.h's + * per-signal sa_mask / Thread.sigHandlerDepth stack): spawns nesttarget.c, + * and, through three real happens-before rendezvous pipes (never a sleep + * guess), sends it sigUsr1, waits for genuine confirmation that sigUsr1's + * handler has actually entered and blocked, and ONLY THEN sends sigUsr2 -- + * proving sigUsr2 interrupts an ALREADY-ACTIVE handler with a second one of + * its own, rather than either being dropped, deferred until sigUsr1 returns + * (which is what an empty-but-still-"one handler at a time" gate, the + * architecture this replaced, would have done), or racing ahead of sigUsr1 + * even starting. + * + * userSigSend's pending-bit write happens synchronously, inside the syscall + * itself, before it ever returns to this process -- so by the time it + * returns 0 below, sigUsr2 is unconditionally pending against nesttarget + * regardless of exactly what nesttarget's own thread is doing at that + * instant. Only the goFd write that follows needs to be strictly ordered + * after it, which normal program order on THIS process already guarantees. + */ + +int main(void) +{ + print("nesttest: start, pid="); + printDec(userGetpid()); + print("\n"); + + long readyHandle = userPipeCreate(); + long handlerReadyHandle = userPipeCreate(); + long goHandle = userPipeCreate(); + if (readyHandle < 0 || handlerReadyHandle < 0 || goHandle < 0) { + print("nesttest: FAIL create pipes\n"); + return 1; + } + + /* Attach every end THIS process owns before nesttarget even exists -- + same "attach first" discipline as jobtest.c, so none of these three + slots can be reclaimed by a same-instant close on the other side. */ + long readyFd = userPipeAttach(readyHandle, 0); + long handlerReadyFd = userPipeAttach(handlerReadyHandle, 0); + long goFd = userPipeAttach(goHandle, 1); + if (readyFd < 0 || handlerReadyFd < 0 || goFd < 0) { + print("nesttest: FAIL attach pipes\n"); + return 1; + } + + char readyHandleStr[24]; + char handlerReadyHandleStr[24]; + char goHandleStr[24]; + decToString(readyHandle, readyHandleStr); + decToString(handlerReadyHandle, handlerReadyHandleStr); + decToString(goHandle, goHandleStr); + + char* targetArgv[] = { "/bin/nesttarget", readyHandleStr, + handlerReadyHandleStr, goHandleStr, 0 }; + long target = userSpawn("/bin/nesttarget", targetArgv); + if (target < 0) { + print("nesttest: FAIL spawn nesttarget\n"); + return 1; + } + + char readyByte; + if (userRead((int)readyFd, &readyByte, 1) != 1) { + print("nesttest: FAIL read ready byte\n"); + return 1; + } + userClose((int)readyFd); + print("nesttest: nesttarget ready\n"); + + if (userSigSend(target, sigUsr1) != 0) { + print("nesttest: FAIL send sigUsr1 to nesttarget\n"); + return 1; + } + print("nesttest: sent sigUsr1 to nesttarget\n"); + + char handlerReadyByte; + /* Real blocking read: does not return until onUsr1 (nesttarget.c) has + actually started running AND is about to block on goFd -- the + happens-before guarantee that makes the nested-delivery proof below + meaningful instead of a coincidence of timing. */ + if (userRead((int)handlerReadyFd, &handlerReadyByte, 1) != 1) { + print("nesttest: FAIL read handler-ready byte\n"); + return 1; + } + userClose((int)handlerReadyFd); + print("nesttest: nesttarget usr1-handler-entered\n"); + + if (userSigSend(target, sigUsr2) != 0) { + print("nesttest: FAIL send sigUsr2 to nesttarget\n"); + return 1; + } + print("nesttest: sent sigUsr2 to nesttarget\n"); + + if (userWrite((int)goFd, "g", 1) != 1) { + print("nesttest: FAIL write go byte\n"); + return 1; + } + userClose((int)goFd); + + long status = userWait(target); + print("nesttest: nesttarget exit status="); + printDec(status); + print("\n"); + if (status != 77) { + print("nesttest: FAIL expected nesttarget exit status 77\n"); + return 1; + } + print("nesttest: nested-signal-delivery-ok\n"); + + return 0; +}