feat: add jobctlfg and jobtarget
This commit is contained in:
1 parent
7f4eb8bfa4
commit
a8819f99c6
3 files changed
+167
No files matched your search
@@ -0,0 +1,54 @@
|
|||||||
|
#include "ulib.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Never spawned directly by kmain.c the way every other boot demo in this
|
||||||
|
* kernel is -- it just sits in /bin like any other binary until kmain.c's
|
||||||
|
* jobctlDriverThread types "jobctlfg" at the REAL interactive shell
|
||||||
|
* (shell.c) through injected keystrokes (same "no automated way to drive a
|
||||||
|
* real PS/2 device under headless QEMU" workaround every other keyboard
|
||||||
|
* demo in this file already uses). shell.c's runProgram falls back to
|
||||||
|
* jobctlSpawnForeground (jobctl.c) for any word that isn't one of its
|
||||||
|
* built-ins, which is the first time this kernel has ever loaded a binary
|
||||||
|
* on demand from user input rather than eagerly at boot -- a real shell's
|
||||||
|
* $PATH lookup, not another pre-arranged spawnUser call.
|
||||||
|
*
|
||||||
|
* jobctlSpawnForeground makes this process its own new group leader and
|
||||||
|
* calls keyboardSetForegroundPgid on its behalf BEFORE the shell blocks
|
||||||
|
* waiting for it (jobctlWaitForeground) -- so this file's proof is exactly
|
||||||
|
* pgroupfg.c's own (an uncaught, real, injected Ctrl-C actually reaches
|
||||||
|
* "the foreground" and terminates it by default disposition), but now
|
||||||
|
* "the foreground" was granted by the SHELL's own runtime job-control
|
||||||
|
* decision instead of a single hardcoded kmain.c call. No handler
|
||||||
|
* installed for sigInt at all: the real-world default disposition
|
||||||
|
* (signal.c's sigDefaultIsIgnore does not list sigInt) is Terminate,
|
||||||
|
* exactly like a real uncaught Ctrl-C killing a foreground program.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
print("jobctlfg: start, pid=");
|
||||||
|
printDec(userGetpid());
|
||||||
|
print("\n");
|
||||||
|
|
||||||
|
long victimHandle = userPipeCreate();
|
||||||
|
if (victimHandle < 0) {
|
||||||
|
print("jobctlfg: FAIL create victim pipe\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
long victimFd = userPipeAttach(victimHandle, 0);
|
||||||
|
if (victimFd < 0) {
|
||||||
|
print("jobctlfg: FAIL attach victim pipe\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("jobctlfg: ready, blocking on go pipe\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 shell-granted foreground Ctrl-C
|
||||||
|
never arriving (or arriving but not actually terminating this
|
||||||
|
process) is the only way to get here. */
|
||||||
|
print("jobctlfg: FAIL read returned without being interrupted\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#include "ulib.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only ever spawned by jobtest.c, with two shared pipe handles as argv[1]
|
||||||
|
* (the "ready" pipe, this process writes) and argv[2] (the "go" pipe, this
|
||||||
|
* process reads) -- decToString'd exactly like fifomaster.c's/sigtarget.c's
|
||||||
|
* children. Proves job control (signal.h's SIGSTOP/SIGCONT) works end to
|
||||||
|
* end against a thread that is genuinely Blocked mid-syscall, not just
|
||||||
|
* Runnable: this process parks in a real blocking userRead on the go pipe,
|
||||||
|
* jobtest.c stops it there with a real userSigSend(target, sigStop) (not a
|
||||||
|
* kernel-internal shortcut), writes the byte it is waiting for WHILE it is
|
||||||
|
* stopped, and only then sends sigCont -- the read can only ever complete
|
||||||
|
* through the exact restart-syscall retry loop sysRead already uses
|
||||||
|
* (syscall.c), which only runs again once threadContinue has moved this
|
||||||
|
* thread back to threadRunnable.
|
||||||
|
*
|
||||||
|
* No sleep/timing guess anywhere in this file or jobtest.c: every step is
|
||||||
|
* ordered by a real blocking read or a real userWait, the same
|
||||||
|
* happens-before discipline sigtarget.c's rendezvous pipe already
|
||||||
|
* established for a different race.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
print("jobtarget: start, pid=");
|
||||||
|
printDec(userGetpid());
|
||||||
|
print("\n");
|
||||||
|
|
||||||
|
if (argc != 3) {
|
||||||
|
print("jobtarget: FAIL expected two argv (ready handle, go handle)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long readyHandle = parseDec(argv[1]);
|
||||||
|
long goHandle = parseDec(argv[2]);
|
||||||
|
|
||||||
|
long goFd = userPipeAttach(goHandle, 0);
|
||||||
|
if (goFd < 0) {
|
||||||
|
print("jobtarget: FAIL attach go pipe as reader\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long readyFd = userPipeAttach(readyHandle, 1);
|
||||||
|
if (readyFd < 0) {
|
||||||
|
print("jobtarget: FAIL attach ready pipe as writer\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (userWrite((int)readyFd, "r", 1) != 1) {
|
||||||
|
print("jobtarget: FAIL write ready byte\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
userClose((int)readyFd);
|
||||||
|
print("jobtarget: ready, blocking on go pipe\n");
|
||||||
|
|
||||||
|
char goByte = 0;
|
||||||
|
/* Real blocking read: parks this thread (threadBlocked) until a byte is
|
||||||
|
actually available. jobtest.c raises SIGSTOP against this exact
|
||||||
|
blocking read, then writes the byte, then raises SIGCONT -- the read
|
||||||
|
below can only return once BOTH have happened, since sysRead's own
|
||||||
|
restart-syscall retry (syscall.c) never runs again while this thread
|
||||||
|
is threadStopped instead of threadRunnable. */
|
||||||
|
if (userRead((int)goFd, &goByte, 1) != 1) {
|
||||||
|
print("jobtarget: FAIL read go byte\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
userClose((int)goFd);
|
||||||
|
|
||||||
|
if (goByte != 'g') {
|
||||||
|
print("jobtarget: FAIL wrong go byte\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
print("jobtarget: received go\n");
|
||||||
|
|
||||||
|
return 55;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#include "ulib.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only ever spawned by killtest.c. Attaches as the READ end of a shared
|
||||||
|
* pipe (userPipeCreate/userPipeAttach) that no writer will ever attach to,
|
||||||
|
* so the userRead call below can only ever return by being killed --
|
||||||
|
* proving threadKill (sched.c) genuinely recovers a process that would
|
||||||
|
* otherwise be stuck forever now that sysRead can really block (see
|
||||||
|
* pipe.h/vfs.h's shared-pipe machinery, added the same wave as this).
|
||||||
|
*/
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
print("killvictim: start, pid=");
|
||||||
|
printDec(userGetpid());
|
||||||
|
print("\n");
|
||||||
|
|
||||||
|
long handle = userPipeCreate();
|
||||||
|
if (handle < 0) {
|
||||||
|
print("killvictim: FAIL pipe create\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long fd = userPipeAttach(handle, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
print("killvictim: FAIL attach\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("killvictim: blocking on read forever\n");
|
||||||
|
char buffer[4];
|
||||||
|
userRead((int)fd, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
/* Only reached if userRead somehow returned on its own -- killtest.c
|
||||||
|
never writes to this pipe, so that would itself be a bug. */
|
||||||
|
print("killvictim: FAIL read returned without being killed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user