Autumn 2021
Jerry Cain
PDF
pid_t fork();
pid_t waitpid(pid_t pid, int *status, int options);
int main(int argc, char *argv[]) {
printf("Before.\n");
pid_t pid = fork();
printf("After.\n");
if (pid == 0) {
printf("I am the child, parent will wait for me.\n");
return 110;
} else {
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("Child exited with status %d.\n",
WEXITSTATUS(status));
} else {
printf("Child terminated abnormally.\n");
}
return 0;
}
}
myth59$ ./separate
Before.
After.
After.
I am the child, parent will wait for me.
Child exited with status 110.
myth59$
myth59$ ./separate
Before.
After.
I am the child, parent will wait for me.
After.
Child exited with status 110.
myth59$
Illustration courtesy of Roz Cyrus.
fork
really is (full program is also online right here).printf
gets executed twice. The child is always the first to execute it, because the parent is blocked in its waitpid
call until the child executes in full.int main(int argc, char *argv[]) {
printf("I get printed once!\n");
pid_t pid = fork(); // returns 0 within child, returns pid of child within fork
bool parent = pid != 0;
if ((random() % 2 == 0) == parent) {
sleep(1); // force exactly one of the two to sleep
printf("Ah, naps are the best!\n"); // brag
}
if (parent) waitpid(pid, NULL, 0); // parent shouldn't exit until it knows its child has finished
printf("I get printed twice (this one is being printed from the %s).\n",
parent ? "parent" : "child");
return 0;
}
fork
multiple times, provided it reaps the child processes (via waitpid
) once they exit. Note that we reap processes as they exit without worrying about the order they were spawned! Full program is also online right here.waitpid
. The -1 means want to hear about any child as it exits, so that pids are returned in the order their processes finish.waitpid
returns -1, it sets a global variable called errno
to the constant ECHILD
to signal waitpid
returned -1 because all child processes have terminated. That's the "error" we want.int main(int argc, char *argv[]) {
for (size_t i = 0; i < 8; i++) {
pid_t pid = fork();
assert(pid >= 0);
if (pid == 0) exit(110 + i);
}
for (size_t i = 0; i < 8; i++) {
int status;
pid_t pid = waitpid(-1, &status, 0);
assert(pid > 0);
if (WIFEXITED(status)) {
printf("Child %d exited: status %d\n", pid, WEXITSTATUS(status));
} else {
printf("Child %d exited abnormally.\n", pid);
}
}
assert(waitpid(-1, NULL, 0) == -1 && errno == ECHILD);
return 0;
}
myth60$ ./reap-as-they-exit
Child 3778291 exited: status 110
Child 3778292 exited: status 111
Child 3778293 exited: status 112
Child 3778294 exited: status 113
Child 3778295 exited: status 114
Child 3778296 exited: status 115
Child 3778297 exited: status 116
Child 3778298 exited: status 117
myth60$ ./reap-as-they-exit
Child 3778306 exited: status 110
Child 3778307 exited: status 111
Child 3778308 exited: status 112
Child 3778309 exited: status 113
Child 3778310 exited: status 114
Child 3778311 exited: status 115
Child 3778312 exited: status 116
Child 3778313 exited: status 117
cplayground$ ./reap-as-they-exit
Child 12 exited: status 110
Child 13 exited: status 111
Child 14 exited: status 112
Child 15 exited: status 113
Child 16 exited: status 114
Child 18 exited: status 116
Child 19 exited: status 117
Child 17 exited: status 115
cplayground$ ./reap-as-they-exit
Child 11 exited: status 110
Child 12 exited: status 111
Child 13 exited: status 112
Child 15 exited: status 114
Child 14 exited: status 113
Child 17 exited: status 116
Child 18 exited: status 117
Child 16 exited: status 115
cplayground: The OS looks to randomly schedule child processes, allowing them to complete and be reaped in a somewhat unpredictable order.
myth: The OS appears to be fairly regimented and round-robin in how it selects child processes to run. I ran reap-as-they-exit some 100 times and always got this output.
int main(int argc, char *argv[]) {
pid_t children[8];
for (size_t i = 0; i < 8; i++) {
if ((children[i] = fork()) == 0) exit(110 + i);
}
for (size_t i = 0; i < 8; i++) {
int status;
pid_t pid = waitpid(children[i], &status, 0);
assert(pid == children[i]);
assert(WIFEXITED(status) && (WEXITSTATUS(status) == (110 + i)));
printf("Child with pid %d accounted for (return status of %d).\n",
children[i], WEXITSTATUS(status));
}
return 0;
}
Below is a sample run of the reap-in-fork-order executable. The pids change between runs, of course, but even those are guaranteed to be published in increasing order.
int main(int argc, char *argv[]) {
pid_t children[8];
for (size_t i = 0; i < 8; i++) {
if ((children[i] = fork()) == 0) exit(110 + i);
}
for (size_t i = 0; i < 8; i++) {
int status;
pid_t pid = waitpid(children[i], &status, 0);
assert(pid == children[i]);
assert(WIFEXITED(status) && (WEXITSTATUS(status) == (110 + i)));
printf("Child with pid %d accounted for (return status of %d).\n",
children[i], WEXITSTATUS(status));
}
return 0;
}
myth60$ ./reap-as-they-exit
Child with pid 3787749 accounted for (return status of 110).
Child with pid 3787750 accounted for (return status of 111).
Child with pid 3787751 accounted for (return status of 112).
Child with pid 3787752 accounted for (return status of 113).
Child with pid 3787753 accounted for (return status of 114).
Child with pid 3787754 accounted for (return status of 115).
Child with pid 3787755 accounted for (return status of 116).
Child with pid 3787756 accounted for (return status of 117).
myth60$
Enter the execvp
system call!
execvp
effectively reboots a process to run a different program from scratch. Here is the full prototype:
path
is relative or absolute pathname of the executable to be invoked.argv
is the argument vector that should be funneled through to the new executable's main
function.path
and argv[0]
generally end up being the same exact string.execvp
fails to cannibalize the process and install a new executable image within it, it returns -1 to express failure.execvp
succeeds, it 😱 never returns 😱.execvp
has many variants (execle
, execlp
, and so forth. Type man
execvp
to see all of them). We typically rely on execvp
in this course.int execvp(const char *path, char *argv[]);
execvp
? An implementation mysystem
to imitate a similar libc
function called system
.
mysystem
function, which executes the supplied command
as if we typed it out in the terminal ourselves, ultimately returning once the surrogate command
has finished.static void mysystem(char *command) {
pid_t pid = fork();
if (pid == 0) {
char *arguments[] = {"/bin/sh", "-c", command, NULL};
execvp(arguments[0], arguments);
printf("Failed to invoked /bin/sh to execute the supplied command.\n");
exit(0);
}
int status;
waitpid(pid, &status, 0);
}
mysystem
spawns a child process to execute that task and waits for it to complete.
execvp
, because we know that if it returns at all, it returns a -1. If that happens, we need to handle the error and make sure the child process terminates via an exposed exit(0)
call.execvp
inside the parent and forgo the child process altogether? Because execvp
would consume the calling process, and that's not what we want.static void mysystem(char *command) {
pid_t pid = fork();
if (pid == 0) {
char *arguments[] = {"/bin/sh", "-c", command, NULL};
execvp(arguments[0], arguments);
printf("Failed to invoked /bin/sh to execute the supplied command.\n");
exit(0);
}
int status;
waitpid(pid, &status, 0);
}
mysystem
fgets
is an overflow-safe variant on scanf
that knows to read everything up through and including the newline character.
mysystem
.static const size_t kMaxLine = 2048;
int main(int argc, char *argv[]) {
char command[kMaxLine];
while (true) {
printf("> ");
fgets(command, kMaxLine, stdin);
if (feof(stdin)) break;
command[strlen(command) - 1] = '\0'; // overwrite '\n'
mysystem(command);
}
printf("\n");
return 0;
}