Truman`s slides

CS110 – Lecture 6
Exceptional Control Flow:
Fork, Wait
Announcements:
●
Assignment 1 due Monday at midnight
 There are *no* late days allowed for this
assignment.
 Make sure to test run the submit script early
so that you don't run into trouble at the last
second.
Assignment 2 will go out that day.
 You will take your assignment 1 code and
optimize it, so make sure you're happy with
the code you have for assignment 1
●
Topics for Today
1) See an example of a real-world program
that uses fork extensively.
2) Finish up Jerry's fork examples
3) Introduce the new waitpid system call that
lets us clean up or “reap” the resources
used by terminated “zombie” processes.
What can we do with fork?
What can we do with fork?
What can we do with fork?
What can we do with fork?
What can we do with fork?
What can we do with fork?
What can we do with fork?
What can we do with fork?
What can we do with fork?
To recap:
●
●
We can use a master process to fork off
multiple logically separate subtasks in a
way that if one crashes it doesn't crash the
rest of the program.
We can use information in the parent
process to detect and report errors in the
child processes.
Fork
Where we left off...
(lecture 4 – slide 2)
static const int kForkFailed = 1;
int main(int argc, char *argv[]) {
printf("Greetings from process %d! (parent %d)\n", getpid(), getppid());
pid_t pid = fork();
exitIf(pid == -1, kForkFailed, stderr, "fork function failed.\n");
printf("Bye-bye from process %d! (parent %d)\n", getpid(), getppid());
return 0;
}
Fork
static const int kForkFailed = 1;
int main(int argc, char *argv[]) {
printf("Greetings from process %d! (parent %d)\n", getpid(), getppid());
pid_t pid = fork();
exitIf(pid == -1, kForkFailed, stderr, "fork function failed.\n");
printf("Bye-bye from process %d! (parent %d)\n", getpid(), getppid());
return 0;
}
Fork-puzzle
//From lecture 4 – slide 3
static const char const *kTrail = "abcd";
static const int kForkFail = 1;
int main(int argc, char *argv[]) {
size_t trailLength = strlen(kTrail);
for (size_t i = 0; i < trailLength; i++) {
printf("%c\n", kTrail[i]);
pid_t pid = fork();
exitIf(pid == -1, kForkFail, stderr, "Call to fork failed.");
}
return 0;
}
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
printf(“b\n”);
fork();
printf(“c\n”);
fork();
printf(“d\n”);
return 0;
}
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
printf(“b\n”);
fork();
printf(“c\n”);
fork();
printf(“d\n”);
return 0;
}
“a”
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
printf(“b\n”);
fork();
printf(“c\n”);
fork();
printf(“d\n”);
return 0;
}
“a”
fork
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
printf(“b\n”);
fork();
printf(“c\n”);
“b”
fork();
printf(“d\n”);
return 0;
}
“a”
“b”
fork
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
Don't forget that both
processes will call fork()!
printf(“b\n”);
fork();
printf(“c\n”);
“b”
fork();
printf(“d\n”);
return 0;
}
“a”
“b”
fork
fork
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
printf(“b\n”);
“c”
fork();
printf(“c\n”);
“b”
“c”
fork();
printf(“d\n”);
return 0;
}
“c”
“a”
“b”
fork
“c”
fork
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
printf(“b\n”);
“c”
fork();
printf(“c\n”);
“b”
“c”
fork();
printf(“d\n”);
return 0;
}
“c”
“a”
“b”
fork
“c”
fork
fork
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
printf(“b\n”);
“c”
fork();
printf(“c\n”);
“b”
“c”
fork();
printf(“d\n”);
return 0;
}
“d”
“d”
“d”
“d”
“d”
“d”
“d”
“d”
“c”
“a”
“b”
fork
“c”
fork
fork
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
Key question: What guarantees
do we have about the ordering
of these print statements?
printf(“b\n”);
“c”
fork();
printf(“c\n”);
“b”
“c”
fork();
printf(“d\n”);
return 0;
}
“d”
“d”
“d”
“d”
“d”
“d”
“d”
“d”
“c”
“a”
“b”
fork
“c”
fork
fork
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
Next key question: Where did
that shell prompt come from?
printf(“b\n”);
“c”
fork();
printf(“c\n”);
“b”
“c”
fork();
printf(“d\n”);
return 0;
}
“d”
“d”
“d”
“d”
“d”
“d”
“d”
“d”
“c”
“a”
“b”
fork
“c”
fork
fork
Fork-puzzle (simplified)
int main(int argc, char *argv[]) {
printf(“a\n”);
fork();
Next key question: Where did
that shell prompt come from?
printf(“b\n”);
“d”
“c”
fork();
printf(“c\n”);
“b”
“d”
“d”
“c”
“d”
fork();
“c”
printf(“d\n”);
return 0;
}
“a” “b”
“c”
“d”
“d”
“d”
“d”
prompt()
tcsh
wait
Wait
Meet your new system call:
pid_t waitpid(pid_t pid, int *status, int options);
Wait
Meet your new system call:
pid_t waitpid(pid_t pid, int *status, int options);
Brief PSA:
For meticulous detailed information about anything you would need to
know about waitpid you can refer to the manual page by running “man
waitpid” in your shell, or check out section 1.4.3 in the B & O reader, or
8.4.3 in the full textbook.
Wait
Meet your new system call:
pid_t waitpid(pid_t pid, int *status, int options);
If we call waitpid from the parent process and pass in the pid of its child,
then we will block the parent process, pausing execution until that child
exits.
When the child exits, that process enters a “zombie” state that, despite
having terminated, will continue to take up resources until we “reap” it
by calling wait.
(We'll talk about the return value, as well as the status and options
arguments in a little bit)
Simple wait example
I've omitted the error checking here for brevity. Do *not* do this
in your assignments! See lecture 4 – slide 4 for the real code.
int main(int argc, char *argv[]) {
printf("I get printed once!\n”);
pid_t pid = fork();
bool parent = (pid != 0);
if ((random() % 2 == 0) == parent) sleep(1); // Force one to sleep
if (parent) waitpid(pid, NULL, 0);
printf(“I'm printing from the %s.\n”, parent ? “parent” : “child”);
return 0;
}
Simple wait example
int main(int argc, char *argv[]) {
printf("I get printed once!\n”);
2 Important Notes:
1) The only difference between the
two processes is the pid returned
from fork. (The random number
generator even gave the same
number for both processes!)
pid_t pid = fork();
bool parent = (pid != 0);
if ((random() % 2 == 0) == parent) sleep(1); // Force one to sleep
if (parent) waitpid(pid, NULL, 0);
printf(“I'm printing from the %s.\n”, parent ? “parent” : “child”);
return 0;
}
Simple wait example
int main(int argc, char *argv[]) {
2 Important Notes:
2) By calling waitpid we guarantee
that the child prints and exits
before the parent does.
printf("I get printed once!\n”);
pid_t pid = fork();
bool parent = (pid != 0);
if ((random() % 2 == 0) == parent) sleep(1); // Force one to sleep
if (parent) waitpid(pid, NULL, 0);
printf(“I'm printing from the %s.\n”, parent ? “parent” : “child”);
return 0;
}
Wait – with status
pid_t waitpid(pid_t pid, int *status, int options);
We can pass in the address of an int variable as the status parameter
and waitpid will give us information about how and why our child
process exited.
(If we pass in NULL it's not an error, wait just assumes we aren't
interested in the information)
Wait – with status
(See separate.c on lecture 5 slide 2 for the real error-checking)
int main(int argc, char *argv[]) {
pid_t pid = fork();
if (pid == 0) {
return 110; // contrived status number
} 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;
}
}
Wait – with status
(See separate.c on lecture 5 slide 2 for the
real error-checking)
2 Takeaways:
int main(int argc, char *argv[]) {
1) If we want the parent and child
pid_t pid = fork();
processes to do different things,
if (pid == 0) {
we have to use the pid to send
return 110; // contrived status number
them down different control
} else {
paths.
int status;
2) We can use macros like
waitpid(pid, &status, 0);
WIFEXITED and WEXITSTATUS
if (WIFEXITED(status)) {
to extract
useful information from
printf(“Child exited with status %d.\n”,
WEXITSTATUS(status));
the status integer.
} else {
(For a list of all of these macros
printf(“Child terminated abnormally\n”);
and their correct use, check the
}
man page or chapter 1.4.3 / 8.4.3
return 0;
of B & O)
}
}
Wait – return value
pid_t waitpid(pid_t pid, int *status, int options);
waitpid will return the pid of the child that was reaped if successful, or -1
in the case of an error.
(It can also return 0 if we use some special options values, but we'll get
to that later).
Wait – Multiple children
(See reap-in-fork-order.c on lecture 5 slide 4 for the full version)
int main(int argc, char *argv[]) {
pid_t children[kNumChildren];
for (size_t i = 0; i < kNumChildren; i++) {
children[i] = fork();
if (children[i] == 0) exit(110 + i); // Using exit is like returning from main
}
for (size_t i = 0; i < kNumChildren; i++) {
int status;
pid_t pid = waitpid(childen[i], &status, 0);
assert(pid == children[i]); // Must have the correct child
assert(WIFEXITED(status) && WEXITSTATUS(status) == 110 + i);
}
return 0;
}
Wait – Multiple children
(See reap-in-fork-order.c on lecture 5 slide
4 for the full version)
2 Takeaways:
int main(int argc, char *argv[]) {
1) We need to use both the return
pid_t children[kNumChildren];
value of wait and the value in
for (size_t i = 0; i < kNumChildren; status
i++) { to do robust error
children[i] = fork();
checking.
if (children[i] == 0) exit(110 + i);
// Using
like
from main
2)We
will exit
waitison
allreturning
of the children
}
in the order they were forked.
for (size_t i = 0; i < kNumChildren; That
i++) means
{
that even if children
int status;
2-8 exit first, we can't reap them
pid_t pid = waitpid(childen[i], &status, 0);
until child 1 exits.
assert(pid == children[i]); // Must have the correct child
assert(WIFEXITED(status) && WEXITSTATUS(status) == 110 + i);
}
return 0;
}
Wait – the “wait set”
pid_t waitpid(pid_t pid, int *status, int options);
Rather than force the parent to wait for a particular child, maybe we
simply want to wait for any child.
We can pass -1 for pid as a sign that we want to wait for any process in
our wait set, which is all of our children by default.
Wait – Multiple children
(See reap-as-they-exit.c on lecture 5 slide 3 for the full version)
int main(int argc, char *argv[]) {
for (size_t i = 0; i < 8; i++) {
pid_t pid = fork();
if (pid == 0) exit(110 + i); // Using exit is like returning from main
}
while (true) {
int status;
pid_t pid = waitpid(-1, &status, 0); // wait for any child
if (pid == -1) break; // Either an error, or no more children
if (WIFEXITED(status)) {
printf(“Child %d exited with status: %d.\n”, pid, WEXITSTATUS(status));
} else {
printf(“Child %d terminated abnormally\n”, pid);
}
}
assert(errno == ECHILD);
return 0;
}
Wait – Multiple children
(See reap-as-they-exit.c on lecture 5 slide
3 for the full version)
2 Takeaways:
int main(int argc, char *argv[]) {
1) We don't know what pid we are
for (size_t i = 0; i < 8; i++) {
expecting from waitpid anymore,
pid_t pid = fork();
onlyfrom
need
if (pid == 0) exit(110 + i); // Using exit is so
like we
returning
mainto ensure it isn't
}
-1 to signal an error.
while (true) {
2) When the operating system
int status;
that we have no more
pid_t pid = waitpid(-1, &status, 0); // waitknows
for any child
if (pid == -1) break; // Either an error, or no
more children
children
to wait for, waitpid
if (WIFEXITED(status)) {
returns -1, signaling an error. It
printf(“Child %d exited with status: %d.\n”, pid, WEXITSTATUS(status));
also sets the global variable
} else {
errno
printf(“Child %d terminated abnormally\n”,
pid); to ECHILD. We want to
}
check that errno == ECHILD to
return 0;
ensure we didn't exit for some
}
other error.
assert(errno == ECHILD);
}
Recap:
pid_t fork()
pid_t waitpid(pid_t pid, int *status, int options);
You now know how to use fork() to create new processes that are a
clone of the current process.
And you've seen how to use waitpid to help manage and clean up these
child processes from the parent process.
Next time:
int execvp(const char *path, char *argv[]);
You'll learn how to use the very cool execvp function to take a process
and replace all of its data with that of some new program.
The vast majority of fork() calls that happen in real code create a clone
just so that it can be immediately replaced with something else.
Then we will use the trio of fork, waitpid, and execvp to implement a
shell just like the one you use when you ssh into myth!