Consider the C program below. (For space reasons, we are not checking error return codes, so assume that all functions return normally.)
int main () {
if (fork() == 0) {
if (fork() == 0) {
printf("3");
}
else {
pid_t pid; int status;
if ((pid = wait(&status)) > 0) {
printf("4");
}
}
}
else {
printf("2");
exit(0);
}
printf("0");
return 0;
}
For each of the following strings, circle whether (Y) or not (N) this string is a possible output of the program.
A. 32040 Y N
B. 34002 Y N
C. 30402 Y N
D. 23040 Y N
E. 40302 Y N
2. Consider the following C program below. (For space reasons, we are not checking error return codes, so assume that all functions return normally.) Note: the atexit function takes a pointer to a function and adds it to a list of functions (initially empty) that will be called when the exit function is called.
void end(void) {
printf(“2”);
}
int main() {
if (fork() == 0)
atexit(end);
if (fork() == 0)
printf(“0”);
else
printf(“1”);
exit(0);
}
For each of the following strings, circle whether (Y) or not (N) this string is a possible output of the program.
A. 112002 Y N
B. 211020 Y N
C. 102120 Y N
D. 122001 Y N
E. 100212 Y N
We covered fork, exec, and wait in lecture. For the shell assignment, you'll also need to know about signals. A signal is a message from the kernel to the process that notifies the process that some event has happened. There are three signals you will need to know about and handle for your lab.
SIGINT | Interrupt: received ctrl-c from the keyboard. |
SIGTSTP | Suspend execution: received ctl-z from the keyboard. |
SIGCHLD | A child process has stopped or terminated. |
A program specifies that it wants to handle a signal by using the UNIX signal call:
#include <signal.h>
typedef void handler_t(void);
handler_t *signal(int signum, handler_t *handler);
The first parameter to signal is the signal number, and the second is the function that should be called when the signal is received. Signal numbers are defined in signal.h.
There are three important points to remember when handling signals:
In particular, the last point is burdensome to deal with. Because the exact semantics of signal differ from system to system, we provide a wrapper, Signal, that only causes signals to be blocked when their signal handlers are currently being executed and that restarts interrupted system calls.
handler_t *Signal(int signum, handler_t *handler)
{
struct sigaction action, old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask); // block sigs of type being handled
action.sa_flags = SA_RESTART; // restart syscalls if possible
if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}
Let's say you're writing a UNIX program that forks multiple children -- a shell perhaps. What happens if your SIGCHLD signal handler is called because a child dies, and while it's executing, several more children die? How can you make sure you clean up after all deceased children?
The following program adds a signal handler that catches SIGINT (ctrl-c) and prints out a message:
#include "csapp.h"
void sigint_handler(int sig);
int main(int argc, char **argv) {
Signal(SIGINT, sigint_handler);
for (;;);
}
void sigint_handler(int sig) {
printf("No thanks!\n");
}
Signals are sent to other processes using the kill function:
#include <signal.h>
int kill(pid_t pid, int sig);
If pid is negative, the signal is sent to all processes in the process group whose parent is abs(pid).
Consider the following situation in which a program forks children but also want to maintain state associated with each child.
This is an example of a race condition. One way of avoiding this particular race condition is to temporarily block the SIGCHLD signal, and then unblock it when we're ready to handle it. This can be done with sigprocmask and related functions:
#include <signal.h>
int sigprocmask(int how, const sigset_t* set,
sigset_t* oldest);
int sigemptyset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
sigemptyset | Initialize a signal set to be empty. |
sigaddset | Add a signal to a signal set. |
sigprocmask | Change the set of currently blocked signals. If how is SIG_BLOCK, block the specified signals. If how is SIG_UNBLOCK, unblock them. The third parameter, if non-NULL, specifies a destination for the previous signal mask. |
3. Consider the following C program. (For space reasons, we are not checking error return codes. You can assume that all functions return normally.)
int val = 10;
void handler(sig) {
val += 5;
return;
}
int main() {
int pid;
signal(SIGCHLD, handler);
if ((pid = fork()) == 0) {
val -= 3;
exit(0);
}
waitpid(pid, NULL, 0);
printf("val = %d\n", val);
exit(0);
}
What is the output of this program? val = ____________
4. Consider the following program:
pid_t pid;
int counter = 0;
void handler1(int sig)
{
counter ++;
printf("counter = %d\n", counter);
fflush(stdout); /* Flushes the printed string to stdout */
kill(pid, SIGUSR1);
}
void handler2(int sig)
{
counter += 3;
printf("counter = %d\n", counter);
exit(0);
}
main() {
signal(SIGUSR1, handler1);
if ((pid = fork()) == 0) {
signal(SIGUSR1, handler2);
kill(getppid(), SIGUSR1);
while(1) {};
}
else {
pid_t p; int status;
if ((p = wait(&status)) > 0) {
counter += 4;
printf("counter = %d\n", counter);
}
}
}
What is the output of this program?