Exceptional Control Flow(7)
example codes:
#include "csapp.h" void handler1(int sig) { pid_t pid; if ((pid = waitpid(-1, NULL, 0)) < 0) unix_error("waitpid error") printf("Handler reaped child %d\n", (int)pid);
//version 2 for the above codes:
pid_t pid;
while ((pid = waitpid(-1, NULL, 0)) > 0)
printf("Handler reaped child %d\n", (int)pid);
if (errno != ECHILD)
unix_error("waitpid error");
//Version 4 code in purple, same with version 2
Sleep(2); return; }
int main()
{ int i, n; char buf[MAXBUF]; if (signal(SIGCHLD, handler1) == SIG_ERR) unix_error("signal error"); /* Parent creates children */ for (i = 0; i < 3; i++) { if (Fork() == 0) { printf("Hello from child %d\n", (int)getpid()); Sleep(1); exit(0);
}
} /* Parent waits for terminal input and then processes it */ if ((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0) unix_error("read");
// version 3 codes for above :
/* Manually restart the read call if it is interrupted */
while ((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0) if (errno != EINTR)
unix_error("read error");
//version4 codes:
/* Parent waits for terminal input and then processes it */
if ((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0)
unix_error("read");
printf("Parent processing input\n"); while (1) ; exit(0); }
however, we get the following output:
linux> ./signal1 Hello from child 10320 Hello from child 10321 Hello from child 10322 Handler reaped child 10320 Handler reaped child 10322 <cr> Parent processing input
From the output, we note that although three SIGCHLD signals were sent to the parent, only two of these signals were received, and thus the parent only reaped two children.
If we suspend the parent process, we see that, indeed, child process 10321 was never reaped and remains a zombie (indicated by the string “defunct” in the output of the ps command):
<ctrl-z> Suspended linux> ps PID TTY STAT TIME COMMAND ... 10319 p5 T 0:03 signal1 10321 p5 Z 0:00 signal1 <defunct> 10323 p5 R 0:00 ps
The problem is that our code failed to account for the facts that signals can block and that signals are not queued.
Here’s what happened: The first signal is received and caught by the parent. While the handler is still processing the first signal, the second signal is delivered and added to the set of pending signals. However, since SIGCHLD signals are blocked by the SIGCHLD handler, the second signal is not received.
Shortly thereafter, while the handler is still processing the first signal, the third signal arrives. Since there is already a pending SIGCHLD, this third SIGCHLD signal is discarded.
Sometime later, after the handler has returned, the kernel notices that there is a pending SIGCHLD signal and forces the parent to receive the signal.
The parent catches the signal and executes the handler a second time. After the handler finishes processing the second signal, there are no more pending SIGCHLD signals, and there never will be, because all knowledge of the third SIGCHLD has been lost.
The crucial lesson is that signals cannot be used to count the occurrence of events in other processes.
To fix the problem, we must recall that the existence of a pending signal only implies that at least one signal has been delivered since the last time the process received a signal of that type.
So we must modify the SIGCHLD handler to reap as many zombie children as possible each time it is invoked.(see the colored-red codes)
However, we are not finished yet. If we run the signal2 program on an older version of the Solaris operating system, it correctly reaps all of the zombie children.
However, now the blocked read system call returns prematurely with an error, before we are able to type in our input on the keyboard:
The problem arises because on this particular Solaris system, slow system calls such as read are not restarted automatically after they are interrupted by the delivery of a signal.
Instead, they return prematurely to the calling application with an error condition, unlike Linux systems, which restart interrupted system calls automatically.
In order to write portable signal handling code, we must allow for the pos- sibility that system calls will return prematurely and then restart them manually when this occurs.
The EINTR return code in errno indicates that the read system call returned prematurely after it was interrupted. (blue codes)
Portable Signal Handling
Posix standard defines the sigaction function, which allows users on Posix- compliant systems such as Linux and Solaris to clearly specify the signal handling semantics they want.
The sigaction function is unwieldy because it requires the user to set the entries of a structure.
A cleaner approach, originally proposed by W. Richard Stevens [109], is to define a wrapper function, called Signal, that calls sigaction for us.
Figure 8.34 shows the definition of Signal, which is invoked in the same way as the signal function.
The Signal wrapper installs a signal handler with the following signal handling semantics:
(1) Only signals of the type currently being processed by the handler are blocked.
(2) As with all signal implementations, signals are not queued.
(3) Interrupted system calls are automatically restarted whenever possible.
(4) Once the signal handler is installed, it remains installed until Signal is called with a handler argument of either SIG_IGN or SIG_DFL.
(Some older Unix systems restore the signal action to its default action after a signal has been processed by a handler.)
Figure 8.35 shows a version of the signal2 program from Figure 8.32 that uses our Signal wrapper to get predictable signal handling semantics on different computer systems.
The only difference is that we have installed the handler with a call to Signal rather than a call to signal.
The program now runs correctly on both our Solaris and Linux systems, and we no longer need to manually restart interrupted read system calls.
(version4 code in purple)
from docs(http://man7.org/linux/man-pages/man2/sigaction.2.html):
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
The sigaction() system call is used to change the action taken by a process on receipt of a specific signal.
signum specifies the signal and can be any valid signal except SIGKILL and SIGSTOP.
If act is non-NULL, the new action for signal signum is installed from act. If oldact is non-NULL, the previous action is saved in oldact.
sigaction structure:
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };
On some architectures a union is involved: do not assign to both sa_handler and sa_sigaction.
sa_handler specifies the action to be associated with signum
sa_mask specifies a mask of signals which should be blocked
sa_flags specifies a set of flags which modify the behavior of the signal.
sa_restorer field is not intended for application use
Explicitly Blocking and Unblocking Signals
Applications can explicitly block and unblock selected signals using the sigproc- mask function
The sigprocmask function changes the set of currently blocked signals(blocked bit vector) . The specific behavior depends on the value of how:
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
(1) SIG_BLOCK: Add the signals in set to blocked (blocked = blocked | set).
(2) SIG_UNBLOCK: Remove the signals in set from blocked (blocked = blocked & ~set).
(3) SIG_SETMASK: blocked = set.
If oldset is non-NULL, the previous value of the blocked bit vector is stored in oldset.
other sets operations:
The sigemptyset initializes set to the empty set.
The sigfillset function adds every signal to set.
The sigaddset function adds signum to set,
sigdelset deletes signum from set,
and sigismember returns 1 if signum is a member of set, and 0 if not.