Few people appreciate the insidious nature of asynchronous events until they encounter a problem that is difficult to reproduce. This chapter discusses signals and their effect on processes, emphasizing the concurrent aspects of signal handling. The chapter begins by defining basic signal concepts such as generation and delivery as well as explaining the difference between ignoring a signal and blocking a signal. Sample programs demonstrate how to use signals for notification and how to suspend a process while waiting for a signal. The chapter also covers error handling, signal safety and asynchronous I/O.
A signal is a software notification to a process of an event. A signal is generated when the event that causes the signal occurs. A signal is delivered when the process takes action based on that signal. The lifetime of a signal is the interval between its generation and its delivery. A signal that has been generated but not yet delivered is said to be pending. There may be considerable time between signal generation and signal delivery. The process must be running on a processor at the time of signal delivery.
A process catches a signal if it executes a signal handler when the signal is delivered. A program installs a signal handler by calling sigaction
with the name of a user-written function. The sigaction
function may also be called with SIG_DFL
or SIG_IGN
instead of a handler. The SIG_DFL
means take the default action, and SIG_IGN
means ignore the signal. Neither of these actions is considered to be “catching” the signal. If the process is set to ignore a signal, that signal is thrown away when delivered and has no effect on the process.
The action taken when a signal is generated depends on the current signal handler for that signal and on the process signal mask. The signal mask contains a list of currently blocked signals. It is easy to confuse blocking a signal with ignoring a signal. Blocked signals are not thrown away as ignored signals are. If a pending signal is blocked, it is delivered when the process unblocks that signal. A program blocks a signal by changing its process signal mask, using sigprocmask
. A program ignores a signal by setting the signal handler to SIG_IGN
, using sigaction
.
This chapter discusses many aspects of POSIX signals. Section 8.2 introduces signals and presents examples of how to generate them. Section 8.3 discusses the signal mask and the blocking of signals, and Section 8.4 covers the catching and ignoring of signals. Section 8.5 shows how a process should wait for the delivery of a signal. The remaining sections of the chapter cover more advanced signal handling topics. Section 8.6 discusses interactions between library functions and signal handling, Section 8.7 covers siglongjmp
, and Section 8.8 introduces POSIX asynchronous I/O. Other aspects of signals are covered in other chapters. Section 9.4 covers POSIX realtime signals, and Section 13.5 covers the use of signals with threads.
Every signal has a symbolic name starting with SIG
. The signal names are defined in signal.h
, which must be included by any C program that uses signals. The names of the signals represent small integers greater than 0. Table 8.1 describes the required POSIX signals and lists their default actions. Two signals, SIGUSR1
and SIGUSR2
, are available for users and do not have a preassigned use. Some signals such as SIGFPE
or SIGSEGV
are generated when certain errors occur; other signals are generated by specific calls such as alarm
.
Table 8.1. The POSIX required signals.
signal | description | default action |
---|---|---|
| process abort | implementation dependent |
| alarm clock | abnormal termination |
| access undefined part of memory object | implementation dependent |
| child terminated, stopped or continued | ignore |
| execution continued if stopped | continue |
| error in arithmetic operation as in division by zero | implementation dependent |
| hang-up (death) on controlling terminal (process) | abnormal termination |
| invalid hardware instruction | implementation dependent |
| interactive attention signal (usually Ctrl-C) | abnormal termination |
| terminated (cannot be caught or ignored) | abnormal termination |
| write on a pipe with no readers | abnormal termination |
| interactive termination: core dump (usually Ctrl- | implementation dependent |
| invalid memory reference | implementation dependent |
| execution stopped (cannot be caught or ignored) | stop |
| termination | abnormal termination |
| terminal stop | stop |
| background process attempting to read | stop |
| background process attempting to write | stop |
| high bandwidth data available at a socket | ignore |
| user-defined signal 1 | abnormal termination |
| user-defined signal 2 | abnormal termination |
Generate signals from the shell with the kill
command. The name kill
derives from the fact that, historically, many signals have the default action of terminating the process. The signal_name
parameter is a symbolic name for the signal formed by omitting the leading SIG
from the corresponding symbolic signal name.
SYNOPSIS kill -s signal_name pid... kill -l [exit_status] kill [-signal_name] pid... kill [-signal_number] pid... POSIX:Shell and Utilities
The last two lines of the synopsis list the traditional forms of the kill
command. Despite the fact that these two forms do not follow the POSIX guidelines for command-line arguments, they continue to be included in the POSIX standard because of their widespread use. The last form of kill
supports only the signal_number
values of 0
for signal 0
, 1
for signal SIGHUP
, 2
for signal SIGINT
, 3
for signal SIGQUIT
, 6
for signal SIGABRT
, 9
for signal SIGKILL
, 14
for signal SIGALRM
and 15
for signal SIGTERM
.
Example 8.1.
The following command is the traditional way to send signal number 9 (SIGKILL
) to process 3423.
kill -9 3423
Example 8.3.
The kill -l
command gives a list of the available symbolic signal names. A system running Sun Solaris produced the following sample output.
% kill -l HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM USR1 USR2 CLD PWR WINCH URG POLL STOP TSTP CONT TTIN TTOU VTALRM PROF XCPU XFSZ WAITING LWP FREEZE THAW CANCEL LOST XRES RTMIN RTMIN+1 RTMIN+2 RTMIN+3 RTMAX-3 RTMAX-2 RTMAX-1 RTMAX
Call the kill
function in a program to send a signal to a process. The kill
function takes a process ID and a signal number as parameters. If the pid
parameter is greater than zero, kill
sends the signal to the process with that ID. If pid
is 0, kill
sends the signal to members of the caller’s process group. If the pid
parameter is -1, kill
sends the signal to all processes for which it has permission to send. If the pid
parameter is another negative value, kill
sends the signal to the process group with group ID equal to |pid|
. Section 11.5 discusses process groups.
SYNOPSIS #include <signal.h> int kill(pid_t pid, int sig); POSIX:CX
If successful, kill
returns 0. If unsuccessful, kill
returns –1 and sets errno
. The following table lists the mandatory errors for kill
.
errno | cause |
---|---|
|
|
| caller does not have the appropriate privileges |
| no process or process group corresponds to |
A user may send a signal only to processes that he or she owns. For most signals, kill
determines permissions by comparing the user IDs of caller and target. SIGCONT
is an exception. For SIGCONT
, user IDs are not checked if kill
is sent to a process that is in the same session. Section 11.5 discusses sessions. For security purposes, a system may exclude an unspecified set of processes from receiving the signal.
Example 8.4.
The following code segment sends SIGUSR1
to process 3423
.
if (kill(3423, SIGUSR1) == -1) perror("Failed to send the SIGUSR1 signal");
Normally, programs do not hardcode specific process IDs such as 3423
in the kill
function call. The usual way to find out relevant process IDs is with getpid
, getppid
, getgpid
or by saving the return value from fork
.
Example 8.5.
This scenario sounds grim, but a child process can kill its parent by executing the following code segment.
if (kill(getppid(), SIGTERM) == -1) perror ("Failed to kill parent");
A process can send a signal to itself with the raise
function. The raise
function takes just one parameter, a signal number.
SYNOPSIS #include <signal.h> int raise(int sig); POSIX:CX
If successful, raise
returns 0. If unsuccessful, raise
returns a nonzero error value and sets errno
. The raise
function sets errno
to EINVAL
if sig
is invalid.
Example 8.6.
The following statement causes a process to send the SIGUSR1
signal to itself.
if (raise(SIGUSR1) != 0) perror("Failed to raise SIGUSR1");
A key press causes a hardware interrupt that is handled by the device driver for the keyboard. This device driver and its associated modules may perform buffering and editing of the keyboard input. Two special characters, the INTR
and QUIT
characters, cause the device driver to send a signal to the foreground process group. A user can send the SIGINT
signal to the foreground process group by entering the INTR
character. This user-settable character is often Ctrl-C. The user-settable QUIT
character sends the SIGQUIT
signal.
Example 8.7.
The stty -a
command reports on the characteristics of the device associated with standard input, including the settings of the signal-generating characters. A system running Sun Solaris produced the following output.
% stty -a speed 9600 baud; rows = 57; columns = 103; ypixels = 0; xpixels = 0; eucw 1:0:0:0, scrw 1:0:0:0 intr = ^c; quit = ^|; erase = ^?; kill = ^u; eof = ^d; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^q; stop = ^s; susp = ^z; dsusp = ^y; rprnt = ^r; flush = ^o; werase = ^w; lnext = ^v; -parenb -parodd cs8 -cstopb hupcl cread -clocal -loblk -crtscts -parext -ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -iuclc ixon -ixany -ixoff imaxbel isig icanon -xcase echo echoe echok -echonl -noflsh -tostop echoctl -echoprt echoke -defecho -flusho -pendin iexten opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel
The terminal in Example 8.7 interprets Ctrl-C as the INTR
character. The QUIT
character (Ctrl-| above) generates SIGQUIT
. The SUSP
character (Ctrl-Z above) generates SIGSTOP
, and the DSUSP
character (Ctrl-Y above) generates SIGCONT
.
The alarm
function causes a SIGALRM
signal to be sent to the calling process after a specified number of real seconds has elapsed. Requests to alarm
are not stacked, so a call to alarm
before the previous timer expires causes the alarm to be reset to the new value. Call alarm
with a zero value for seconds
to cancel a previous alarm request.
SYNOPSIS #include <unistd.h> unsigned alarm(unsigned seconds); POSIX
The alarm
function returns the number of seconds remaining on the alarm before the call reset the value, or 0 if no previous alarm was set. The alarm
function never reports an error.
A process can temporarily prevent a signal from being delivered by blocking it. Blocked signals do not affect the behavior of the process until they are delivered. The process signal mask gives the set of signals that are currently blocked. The signal mask is of type sigset_t
.
Blocking a signal is different from ignoring a signal. When a process blocks a signal, the operating system does not deliver the signal until the process unblocks the signal. A process blocks a signal by modifying its signal mask with sigprocmask
. When a process ignores a signal, the signal is delivered and the process handles it by throwing it away. The process sets a signal to be ignored by calling sigaction
with a handler of SIG_IGN
, as described in Section 8.4.
Specify operations (such as blocking or unblocking) on groups of signals by using signal sets of type sigset_t
. Signal sets are manipulated by the five functions listed in the following synopsis box. The first parameter for each function is a pointer to a sigset_t
. The sigaddset
adds signo
to the signal set, and the sigdelset
removes signo
from the signal set. The sigemptyset
function initializes a sigset_t
to contain no signals; sigfillset
initializes a sigset_t
to contain all signals. Initialize a signal set by calling either sigemptyset
or sigfillset
before using it. The sigismember
reports whether signo
is in a sigset_t
.
SYNOPSIS #include <signal.h> int sigaddset(sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigismember(const sigset_t *set, int signo); POSIX:CX
The sigismember
function returns 1 if signo
is in *set
and 0 if signo
is not in *set
. If successful, the other functions return 0. If unsuccessful, these other functions return –1 and set errno
. POSIX does not define any mandatory errors for these functions.
Example 8.9.
The following code segment initializes signal set twosigs
to contain exactly the two signals SIGINT
and SIGQUIT
.
if ((sigemptyset(&twosigs) == -1) || (sigaddset(&twosigs, SIGINT) == -1) || (sigaddset(&twosigs, SIGQUIT) == -1)) perror("Failed to set up signal mask");
A process can examine or modify its process signal mask with the sigprocmask
function. The how
parameter is an integer specifying the manner in which the signal mask is to be modified. The set
parameter is a pointer to a signal set to be used in the modification. If set
is NULL
, no modification is made. If oset
is not NULL
, the sigprocmask
returns in *oset
the signal set before the modification.
SYNOPSIS #include <signal.h> int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); POSIX:CX
If successful, sigprocmask
returns 0. If unsuccessful, sigprocmask
returns –1 and sets errno
. The sigprocmask
function sets errno
to EINVAL
if how
is invalid. The sigprocmask
function should only be used by a process with a single thread. When multiple threads exist, the pthread_sigmask
function (page 474) should be used.
The how
parameter, which specifies the manner in which the signal mask is to be modified, can take on one of the following three values.
| add a collection of signals to those currently blocked |
| delete a collection of signals from those currently blocked |
| set the collection of signals being blocked to the specified set |
Keep in mind that some signals, such as SIGSTOP
and SIGKILL
, cannot be blocked. If an attempt is made to block these signals, the system ignores the request without reporting an error.
Example 8.10.
The following code segment adds SIGINT
to the set of signals that the process has blocked.
sigset_t newsigset; if ((sigemptyset(&newsigset) == -1) || (sigaddset(&newsigset, SIGINT) == -1)) perror("Failed to initialize the signal set"); else if (sigprocmask(SIG_BLOCK, &newsigset, NULL) == -1) perror("Failed to block SIGINT");
If SIGINT
is already blocked, the call to sigprocmask
has no effect.
Program 8.1 displays a message, blocks the SIGINT
signal while doing some useless work, unblocks the signal, and does more useless work. The program repeats this sequence continually in a loop.
If a user enters Ctrl-C while SIGINT
is blocked, Program 8.1 finishes the calculation and prints a message before terminating. If a user types Ctrl-C while SIGINT
is unblocked, the program terminates immediately.
Example 8.1. blocktest.c
A program that blocks and unblocks SIGINT
.
#include <math.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { int i; sigset_t intmask; int repeatfactor; double y = 0.0; if (argc != 2) { fprintf(stderr, "Usage: %s repeatfactor ", argv[0]); return 1; } repeatfactor = atoi(argv[1]); if ((sigemptyset(&intmask) == -1) || (sigaddset(&intmask, SIGINT) == -1)){ perror("Failed to initialize the signal mask"); return 1; } for ( ; ; ) { if (sigprocmask(SIG_BLOCK, &intmask, NULL) == -1) break; fprintf(stderr, "SIGINT signal blocked "); for (i = 0; i < repeatfactor; i++) y += sin((double)i); fprintf(stderr, "Blocked calculation is finished, y = %f ", y); if (sigprocmask(SIG_UNBLOCK, &intmask, NULL) == -1) break; fprintf(stderr, "SIGINT signal unblocked "); for (i = 0; i < repeatfactor; i++) y += sin((double)i); fprintf(stderr, "Unblocked calculation is finished, y=%f ", y); } perror("Failed to change signal mask"); return 1; }
The function makepair
of Program 8.2 takes two pathnames as parameters and creates two named pipes with these names. If successful, makepair
returns 0. If unsuccessful, makepair
returns –1 and sets errno
. The function blocks all signals during the creation of the two pipes to be sure that it can deallocate both pipes if there is an error. The function restores the original signal mask before the return. The if
statement relies on the conditional left-to-right evaluation of &&
and ||
.
Example 8.11.
Is it possible that after a call to makepair
, pipe1
exists but pipe2
does not?
Answer:
Yes. This could happen if pipe1
already exists but pipe2
does not and the user does not have write permission to the directory. It could also happen if the SIGKILL
signal is delivered between the two calls to mkfifo
.
Example 8.2. makepair.c
A function that blocks signals while creating two pipes. (See Exercise 8.11 and Exercise 8.12 for a discussion of some flaws.)
#include <errno.h> #include <signal.h> #include <unistd.h> #include <sys/stat.h> #define R_MODE (S_IRUSR | S_IRGRP | S_IROTH) #define W_MODE (S_IWUSR | S_IWGRP | S_IWOTH) #define RW_MODE (R_MODE | W_MODE) int makepair(char *pipe1, char *pipe2) { sigset_t blockmask; sigset_t oldmask; int returncode = 0; if (sigfillset(&blockmask) == -1) return -1; if (sigprocmask(SIG_SETMASK, &blockmask, &oldmask) == -1) return -1; if (((mkfifo(pipe1, RW_MODE) == -1) && (errno != EEXIST)) || ((mkfifo(pipe2, RW_MODE) == -1) && (errno != EEXIST))) { returncode = errno; unlink(pipe1); unlink(pipe2); } if ((sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) && !returncode) returncode = errno; if (returncode) { errno = returncode; return -1; } return 0; }
Example 8.12.
Does a makepair
return value of 0 guarantee that FIFOs corresponding to pipe1
and pipe2
are available on return?
Answer:
If one of the files already exists, mkfifo
returns –1 and sets errno
to EEXIST
. The makepair
function assumes that the FIFO exists without checking whether the file was a FIFO or an ordinary file. Thus, it is possible for makepair
to indicate success even if this previously existing file is not a FIFO.
In Program 8.3, the parent blocks all signals before forking a child process to execute an ls
command. Processes inherit the signal mask after both fork
and exec
, so the ls
command executes with signals blocked. The child created by fork
in Program 8.3 has a copy of the original signal mask saved in oldmask
. An exec
command overwrites all program variables, so an executed process cannot restore the original mask once exec
takes place. The parent restores the original signal mask and then waits for the child.
Example 8.3. blockchild.c
A program that blocks signals before calling fork
and execl
.
#include <errno.h> #include <stdio.h> #include <signal.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include "restart.h" int main(void) { pid_t child; sigset_t mask, omask; if ((sigfillset(&mask) == -1) || (sigprocmask(SIG_SETMASK, &mask, &omask) == -1)) { perror("Failed to block the signals"); return 1; } if ((child = fork()) == -1) { perror("Failed to fork child"); return 1; } if (child == 0) { /* child code */ execl("/bin/ls", "ls", "-l", NULL); perror("Child failed to exec"); return 1; } if (sigprocmask(SIG_SETMASK, &omask, NULL) == -1){ /* parent code */ perror("Parent failed to restore signal mask"); return 1; } if (r_wait(NULL) == -1) { perror("Parent failed to wait for child"); return 1; } return 0; }
Example 8.13.
Run Program 8.3 from a working directory with a large number of files. Experiment with entering Ctrl-C at various points during the execution and explain what happens.
Answer:
The main
program can be interrupted while the listing is being displayed, and the prompt will appear in the middle of the listing. The execution of ls
will not be interrupted by the signal.
Example 8.4. password.c
A function that retrieves a user password.
#include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <termios.h> #include <unistd.h> #include "restart.h" int setecho(int fd, int onflag); int password(const char *prompt, char *passbuf, int passmax) { int fd; int firsterrno = 0; sigset_t signew, sigold; char termbuf[L_ctermid]; if (ctermid(termbuf) == NULL) { /* find the terminal name */ errno = ENODEV; return -1; } if ((fd = open(termbuf, O_RDONLY)) == -1) /* open descriptor to terminal */ return -1; if ((sigemptyset(&signew) == -1) || /* block SIGINT, SIGQUIT and SIGTSTP */ (sigaddset(&signew, SIGINT) == -1) || (sigaddset(&signew, SIGQUIT) == -1) || (sigaddset(&signew, SIGTSTP) == -1) || (sigprocmask(SIG_BLOCK, &signew, &sigold) == -1) || (setecho(fd, 0) == -1)) { /* set terminal echo off */ firsterrno = errno; sigprocmask(SIG_SETMASK, &sigold, NULL); r_close(fd); errno = firsterrno; return -1; } if ((r_write(STDOUT_FILENO, (char *)prompt, strlen(prompt)) == -1) || (readline(fd, passbuf, passmax) == -1)) /* read password */ firsterrno = errno; else passbuf[strlen(passbuf) - 1] = 0; /* remove newline */ if ((setecho(fd, 1) == -1) && !firsterrno) /* turn echo back on */ firsterrno = errno; if ((sigprocmask(SIG_SETMASK, &sigold, NULL) == -1) && !firsterrno ) firsterrno = errno; if ((r_close(fd) == -1) && !firsterrno) /* close descriptor to terminal */ firsterrno = errno; return firsterrno ? errno = firsterrno, -1: 0; }
Program 8.4 shows an improvement on the passwordnosigs
function of Program 6.13 on page 208. The password
function blocks SIGINT
, SIGQUIT
and SIGTSTP
while terminal echo is set off, preventing the terminal from being placed in an unusable state if one of these signals is delivered to the process while this function is executing.
The sigaction
function allows the caller to examine or specify the action associated with a specific signal. The sig
parameter of sigaction
specifies the signal number for the action. The act
parameter is a pointer to a struct sigaction
structure that specifies the action to be taken. The oact
parameter is a pointer to a struct sigaction
structure that receives the previous action associated with the signal. If act
is NULL
, the call to sigaction
does not change the action associated with the signal. If oact
is NULL
, the call to sigaction
does not return the previous action associated with the signal.
SYNOPSIS #include <signal.h> int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact); POSIX:CX
If successful, sigaction
returns 0. If unsuccessful, sigaction
returns –1 and sets errno
. The following table lists the mandatory errors for sigaction
.
| cause |
---|---|
|
|
|
|
The struct sigaction
structure must have at least the following members.
struct sigaction { void (*sa_handler)(int); /* SIG_DFL, SIG_IGN or pointer to function */ sigset_t sa_mask; /* additional signals to be blocked during execution of handler */ int sa_flags; /* special flags and options */ void(*sa_sigaction) (int, siginfo_t *, void *); /* realtime handler */ };
The storage for sa_handler
and sa_sigaction
may overlap, and an application should use only one of these members to specify the action. If the SA_SIGINFO
flag of the sa_flags
field is cleared, the sa_handler
specifies the action to be taken for the specified signal. If the SA_SIGINFO
flag of the sa_flags
field is set and the implementation supports either the POSIX:RTS or the POSIX:XSI Extension, the sa_sigaction
field specifies a signal-catching function.
Example 8.14.
The following code segment sets the signal handler for SIGINT
to mysighand
.
struct sigaction newact; newact.sa_handler = mysighand; /* set the new handler */ newact.sa_flags = 0; /* no special options */ if ((sigemptyset(&newact.sa_mask) == -1) || (sigaction(SIGINT, &newact, NULL) == -1)) perror("Failed to install SIGINT signal handler");
In the POSIX base standard, a signal handler is an ordinary function that returns void
and has one integer parameter. When the operating system delivers the signal, it sets this parameter to the number of the signal that was delivered. Most signal handlers ignore this value, but it is possible to have a single signal handler for many signals. The usefulness of signal handlers is limited by the inability to pass values to them. This capability has been added to the POSIX:RTS and POSIX:XSI Extensions, which can use the alternative sa_sigaction
field of the struct sigaction
structure to specify a handler. This section describes using the sa_handler
field of sigaction
to set up the handler; Section 9.4 describes using the sa_sigaction
field for the handler.
Two special values of the sa_handler
member of struct sigaction
are SIG_DFL>
and SIG_IGN
. The SIG_DFL
value specifies that sigaction
should restore the default action for the signal. The SIG_IGN
value specifies that the process should handle the signal by ignoring it (throwing it away).
Example 8.15.
The following code segment causes the process to ignore SIGINT
if the default action is in effect for this signal.
struct sigaction act; if (sigaction(SIGINT, NULL, &act) == -1) /* Find current SIGINT handler */ perror("Failed to get old handler for SIGINT"); else if (act.sa_handler == SIG_DFL) { /* if SIGINT handler is default */ act.sa_handler = SIG_IGN; /* set new SIGINT handler to ignore */ if (sigaction(SIGINT, &act, NULL) == -1) perror("Failed to ignore SIGINT"); }
Example 8.16.
The following code segment sets up a signal handler that catches the SIGINT
signal generated by Ctrl-C.
void catchctrlc(int signo) { char handmsg[] = "I found Ctrl-C "; int msglen = sizeof(handmsg); write(STDERR_FILENO, handmsg, msglen); } ... struct sigaction act; act.sa_handler = catchctrlc; act.sa_flags = 0; if ((sigemptyset(&act.sa_mask) == -1) || (sigaction(SIGINT, &act, NULL) == -1)) perror("Failed to set SIGINT to handle Ctrl-C");
Example 8.17.
Why didn’t Example 8.16 use fprintf
or strlen
in the signal handler?
Answer:
POSIX guarantees that write
is async-signal safe, meaning that it can be called safely from inside a signal handler. There are no similar guarantees for fprintf
or strlen
, but they may be async-signal safe in some implementations. Table 8.2 on page 285 lists the functions that POSIX guarantees are async-signal safe.
Example 8.18.
The following code segment sets the action of SIGINT
to the default.
struct sigaction newact; newact.sa_handler = SIG_DFL; /* new handler set to default */ newact.sa_flags = 0; /* no special options */ if ((sigemptyset(&newact.sa_mask) == -1) || (sigaction(SIGINT, &newact, NULL) == -1)) perror("Failed to set SIGINT to the default action");
Example 8.19. testignored.c
The following function takes a signal number parameter and returns 1 if that signal is ignored and 0 otherwise.
#include <signal.h> #include <stdio.h> int testignored(int signo) { struct sigaction act; if ((sigaction(signo, NULL, &act) == -1) || (act.sa_handler != SIG_IGN)) return 0; return 1; }
Program 8.5 estimates the average value of sin(x) on the interval from 0 to 1 by computing the average of the sine of randomly picked values. The main
program loop chooses a random value, x, between 0 and 1, adds sin(x) to a running sum, increments the count of the values, and prints the current count and average. The program illustrates the use of a signal handler to gracefully terminate a program. When the user enters Ctrl-C at standard input, the signal handler sets doneflag
to signify that the program should terminate. On each iteration of the computation loop, the program tests doneflag
to see whether it should drop out of the loop and print a final message.
Example 8.5. signalterminate.c
A program that terminates gracefully when it receives a Ctrl-C.
#include <math.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> static volatile sig_atomic_t doneflag = 0; /* ARGSUSED */ static void setdoneflag(int signo) { doneflag = 1; } int main (void) { struct sigaction act; int count = 0; double sum = 0; double x; act.sa_handler = setdoneflag; /* set up signal handler */ act.sa_flags = 0; if ((sigemptyset(&act.sa_mask) == -1) || (sigaction(SIGINT, &act, NULL) == -1)) { perror("Failed to set SIGINT handler"); return 1; } while (!doneflag) { x = (rand() + 0.5)/(RAND_MAX + 1.0); sum += sin(x); count++; printf("Count is %d and average is %f ", count, sum/count); } printf("Program terminating ... "); if (count == 0) printf("No values calculated yet "); else printf("Count is %d and average is %f ", count, sum/count); return 0; }
Code that accesses doneflag
is a critical section because the signal handler can modify this variable while the main
program examines it. (See Chapter 14 for a discussion of critical sections and atomic operations.) We handle the problem here by declaring doneflag
to be sig_atomic_t
, an integral type that is small enough to be accessed atomically. The volatile
qualifier on doneflag
informs the compiler that the variable may be changed asynchronously to program execution. Otherwise, the compiler might assume that doneflag
is not modified in the while
loop and generate code that only tests the condition on the first iteration of the loop.
Example 8.20.
Why is it okay to use perror
and printf
in Program 8.5 even though these functions are not “signal safe”?
Answer:
Signal safety is a problem when both the signal handler and the main
program use these functions. In this case, only the main
program uses these functions.
When both a signal handler and the main
program need to access data that is larger than sig_atomic_t
, care must be taken so that the data is not modified in one part of the program while being read in another. Program 8.6 also calculates the average value of sin(x) over the interval from 0 to 1, but it does not print the result on each iteration. Instead, the main
program loop generates a string containing the results every 10,000th iteration. A signal handler for SIGUSR1
outputs the string when the user sends SIGUSR1
to the process.
Example 8.6. averagesin.c
A program to estimate the average values of sin(x) over the interval from 0 to 1.
#include <errno.h> #include <limits.h> #include <math.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BUFSIZE 100 static char buf[BUFSIZE]; static int buflen = 0; /* ARGSUSED */ static void handler(int signo) { /* handler outputs result string */ int savederrno; savederrno = errno; write(STDOUT_FILENO, buf, buflen); errno = savederrno; } static void results(int count, double sum) { /* set up result string */ double average; double calculated; double err; double errpercent; sigset_t oset; sigset_t sigset; if ((sigemptyset(&sigset) == -1) || (sigaddset(&sigset, SIGUSR1) == -1) || (sigprocmask(SIG_BLOCK, &sigset, &oset) == -1) ) perror("Failed to block signal in results"); if (count == 0) snprintf(buf, BUFSIZE, "No values calculated yet "); else { calculated = 1.0 - cos(1.0); average = sum/count; err = average - calculated; errpercent = 100.0*err/calculated; snprintf(buf, BUFSIZE, "Count = %d, sum = %f, average = %f, error = %f or %f%% ", count, sum, average, err, errpercent); } buflen = strlen(buf); if (sigprocmask(SIG_SETMASK, &oset, NULL) == -1) perror("Failed to unblock signal in results"); } int main(void) { int count = 0; double sum = 0; double x; struct sigaction act; act.sa_handler = handler; act.sa_flags = 0; if ((sigemptyset(&act.sa_mask) == -1) || (sigaction(SIGUSR1, &act, NULL) == -1) ) { perror("Failed to set SIGUSR1 signal handler"); return 1; } fprintf(stderr, "Process %ld starting calculation ", (long)getpid()); for ( ; ; ) { if ((count % 10000) == 0) results(count, sum); x = (rand() + 0.5)/(RAND_MAX + 1.0); sum += sin(x); count++; if (count == INT_MAX) break; } results(count, sum); handler(0); /* call handler directly to write out the results */ return 0; }
The signal handler uses write
instead of printf
, since printf
may not be safe to use in a signal handler. The handler avoids strlen
for the same reason. The string and its length are global variables accessible to both the main
program and the signal handler. Modifying the string in the main
program and writing the string to standard output in the signal handler are critical sections for this program. The main
program protects its critical section by having results
block the signal while modifying the string and its length. Notice also that handler
saves and restores errno
, since write
may change it.
Legacy programs sometimes use signal
instead of sigaction
to specify signal handlers. Although signal
is part of ISO C, it is unreliable even when used in a program with a single thread. Always use sigaction
to set up your handlers.
Signals provide a method for waiting for an event without busy waiting. Busy waiting means continually using CPU cycles to test for the occurrence of an event. Typically, a program does this testing by checking the value of a variable in a loop. A more efficient approach is to suspend the process until the waited-for event occurs; that way, other processes can use the CPU productively. The POSIX pause
, sigsuspend
and sigwait
functions provide three mechanisms for suspending a process until a signal occurs.
The pause
function suspends the calling thread until the delivery of a signal whose action is either to execute a user-defined handler or to terminate the process. If the action is to terminate, pause
does not return. If a signal is caught by the process, pause
returns after the signal handler returns.
SYNOPSIS #include <unistd.h> int pause(void); POSIX
The pause
function always returns –1. If interrupted by a signal, pause
sets errno
to EINTR
.
To wait for a particular signal by using pause
, you must determine which signal caused pause
to return. This information is not directly available, so the signal handler must set a flag for the program to check after pause
returns.
Example 8.21.
The following code segment uses pause
to cause a process to wait for a particular signal by having the signal handler set the sigreceived
variable to 1. What happens if a signal is delivered between the test of sigreceived
and pause
?
static volatile sig_atomic_t sigreceived = 0; while(sigreceived == 0) pause();
Answer:
The previously delivered signal does not affect pause
. The pause
function does not return until some other signal or another occurrence of the same signal is delivered to the process. A workable solution must test the value of sigreceived
while the signal is blocked.
Example 8.22.
What is wrong with the following attempt to prevent a signal from being delivered between the test of sigreceived
and the execution of pause
in Exercise 8.21?
static volatile sig_atomic_t sigreceived = 0; int signum; sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, signum); sigprocmask(SIG_BLOCK, &sigset, NULL); while(sigreceived == 0) pause();
Answer:
Unfortunately, the code segment executes pause
while the signal is blocked. As a result, the program never receives the signal and pause
never returns. If the program unblocks the signal before executing pause
, it might receive the signal between the unblocking and the execution of pause
. This event is actually more likely than it seems. If a signal is generated while the process has the signal blocked, the process receives the signal right after unblocking it.
The delivery of a signal before pause
was one of the major problems with the original UNIX signals, and there was no simple, reliable way to get around the problem. The program must do two operations “at once”—unblock the signal and start pause
. Another way of saying this is that the two operations together should be atomic (i.e., the program cannot be logically interrupted between execution of the two operations). The sigsuspend
function provides a method of achieving this.
The sigsuspend
function sets the signal mask to the one pointed to by sigmask
and suspends the process until a signal is caught by the process. The sigsuspend
function returns when the signal handler of the caught signal returns. The sigmask
parameter can be used to unblock the signal the program is looking for. When sigsuspend
returns, the signal mask is reset to the value it had before the sigsuspend
function was called.
SYNOPSIS #include <signal.h> int sigsuspend(const sigset_t *sigmask); POSIX:CX
The sigsuspend
function always returns –1 and sets errno
. If interrupted by a signal, sigsuspend
sets errno
to EINTR
.
Example 8.23.
What is wrong with the following code that uses sigsuspend
to wait for a signal?
sigfillset(&sigmost); sigdelset(&sigmost, signum); sigsuspend(&sigmost);
Answer:
The sigmost
signal set contains all signals except the one to wait for. When the process suspends, only the signal signum
is unblocked and so it seems that only this signal can cause sigsuspend
to return. However, the code segment has the same problem that the solution using pause
had. If the signal is delivered before the start of the code segment, the process still suspends itself and deadlocks if another signum
signal is not generated.
Example 8.24.
The following code segment shows a correct way to wait for a single signal. Assume that a signal handler has been set up for the signum
signal and that the signal handler sets sigreceived
to 1.
1 static volatile sig_atomic_t sigreceived = 0; 2 3 sigset_t maskall, maskmost, maskold; 4 int signum = SIGUSR1; 5 6 sigfillset(&maskall); 7 sigfillset(&maskmost); 8 sigdelset(&maskmost, signum); 9 sigprocmask(SIG_SETMASK, &maskall, &maskold); 10 if (sigreceived == 0) 11 sigsuspend(&maskmost); 12 sigprocmask(SIG_SETMASK, &maskold, NULL);
The code omits error checking for clarity.
Example 8.24 uses three signal sets to control the blocking and unblocking of signals at the appropriate time. Lines 6 through 8 set maskall
to contain all signals and maskmost
to contain all signals but signum
. Line 9 blocks all signals. Line 10 tests sigreceived
, and line 11 suspends the process if the signal has not yet been received. Note that no signals can be caught between the testing and the suspending, since the signal is blocked at this point. The process signal mask has the value maskmost
while the process is suspended, so only signum
is not blocked. When sigsuspend
returns, the signal must have been received.
Example 8.25.
The following code segment shows a modification of Example 8.24 that allows other signals to be handled while the process is waiting for signum
.
1 static volatile sig_atomic_t sigreceived = 0; 2 3 sigset_t maskblocked, maskold, maskunblocked; 4 int signum = SIGUSR1; 5 6 sigprocmask(SIG_SETMASK, NULL, &maskblocked); 7 sigprocmask(SIG_SETMASK, NULL, &maskunblocked); 8 sigaddset(&maskblocked, signum); 9 sigdelset(&maskunblocked, signum); 10 sigprocmask(SIG_BLOCK, &maskblocked, &maskold); 11 while(sigreceived == 0) 12 sigsuspend(&maskunblocked); 13 sigprocmask(SIG_SETMASK, &maskold, NULL);
The code omits error checking for clarity.
Instead of blocking all signals and then unblocking only signum
, Example 8.25 does not change the other signals in the signal mask. As before, the sigreceived
variable declared in line 1 is declared outside any block and has static storage class. The code assumes that sigreceived
is modified only in the signal handler for signum
and that signal handler sets the value to 1. Thus, only the delivery of signum
can make this variable nonzero. The rest of the code starting with line 3 is assumed to be inside some function.
The three signal sets declared in line 3 are initialized to contain the currently blocked signals in lines 6, 7 and 10. Line 8 adds the signal signum
to the set maskblocked
if it was not already blocked, and line 9 removes signum
from maskunblocked
if it was not already unblocked. The consequence of these two lines is that maskblocked
contains exactly those signals that were blocked at the start of the code segment, except that signum
is guaranteed to be in this set. Similarly, maskunblocked
contains exactly those signals that were blocked at the start of the code segment, except that signum
is guaranteed not to be in this set.
Line 10 guarantees that the signum
signal is blocked while the value of sigreceived
is being tested. No other signals are affected. The code ensures that sigreceived
does not change between its testing in line 11 and the suspending of the process in line 12. Using maskunblocked
in line 12 guarantees that the signal will not be blocked while the process is suspended, allowing a generated signal to be delivered and to cause sigsuspend
to return. When sigsuspend
does return, the while
in line 11 executes again and tests sigreceived
to see if the correct signal came in. Signals other than signum
may have been unblocked before entry to the code segment and delivery of these signals causes sigsuspend
to return. The code tests sigreceived
each time and suspends the process again until the right signal is delivered. When the while
condition is false, the signal has been received and line 13 executes, restoring the signal mask to its original state.
Example 8.26.
The following code segment shows a shorter, but equivalent, version of the code in Example 8.25.
1 static volatile sig_atomic_t sigreceived = 0; 2 3 sigset_t masknew, maskold; 4 int signum = SIGUSR1; 5 6 sigprocmask(SIG_SETMASK, NULL, &masknew); 7 sigaddset(&masknew, signum); 8 sigprocmask(SIG_SETMASK, &masknew, &maskold); 9 sigdelset(&masknew, signum); 10 while(sigreceived == 0) 11 sigsuspend(&masknew); 12 sigprocmask(SIG_SETMASK, &maskold, NULL);
This code omits error checking for clarity.
Lines 6 and 7 set masknew
to contain the original signal mask plus signum
. Line 8 modifies the signal mask to block signum
. Line 9 modifies masknew
again so that now it does not contain signum
. This operation does not change the process signal mask or the signals that are currently blocked. The signal signum
is still blocked when line 10 tests sigreceived
, but it is unblocked when line 11 suspends the process because of the change made to masknew
on line 9.
The code segment in Example 8.26 assumes that sigreceived
is initially 0 and that the handler for signum
sets sigreceived
to 1. It is important that the signal be blocked when the while
is testing sigreceived
. Otherwise, the signal can be delivered between the test of sigreceived
and the call to sigsuspend
. In this case, the process blocks until another signal causes the sigsuspend
to return.
Example 8.27.
Suppose the sigsuspend
in Example 8.26 returns because of a different signal. Is the signum
signal blocked when the while
tests sigreceived
again?
Answer:
Yes, when sigsuspend
returns, the signal mask has been restored to the state it had before the call to sigsuspend
. The call to sigprocmask
before the while
guarantees that this signal is blocked.
Example 8.7. simplesuspend.c
An object that allows a program to safely block on a specific signal.
#include <errno.h> #include <signal.h> #include <unistd.h> static int isinitialized = 0; static struct sigaction oact; static int signum = 0; static volatile sig_atomic_t sigreceived = 0; /* ARGSUSED */ static void catcher (int signo) { sigreceived = 1; } int initsuspend (int signo) { /* set up the handler for the pause */ struct sigaction act; if (isinitialized) return 0; act.sa_handler = catcher; act.sa_flags = 0; if ((sigfillset(&act.sa_mask) == -1) || (sigaction(signo, &act, &oact) == -1)) return -1; signum = signo; isinitialized = 1; return 0; } int restore(void) { if (!isinitialized) return 0; if (sigaction(signum, &oact, NULL) == -1) return -1; isinitialized = 0; return 0; } int simplesuspend(void) { sigset_t maskblocked, maskold, maskunblocked; if (!isinitialized) { errno = EINVAL; return -1; } if ((sigprocmask(SIG_SETMASK, NULL, &maskblocked) == -1) || (sigaddset(&maskblocked, signum) == -1) || (sigprocmask(SIG_SETMASK, NULL, &maskunblocked) == -1) || (sigdelset(&maskunblocked, signum) == -1) || (sigprocmask(SIG_SETMASK, &maskblocked, &maskold) == -1)) return -1; while(sigreceived == 0) sigsuspend(&maskunblocked); sigreceived = 0; return sigprocmask(SIG_SETMASK, &maskold, NULL); }
Program 8.7 shows an object implementation of functions to block on a specified signal. Before calling simplesuspend
, the program calls initsuspend
to set up the handler for the signal to pause on. The program calls restore
to reset signal handling to the prior state.
Program 8.8 uses the functions of Program 8.7 to wait for SIGUSR1
.
Example 8.8. simplesuspendtest.c
A program that waits for SIGUSR1
.
#include <signal.h> #include <stdio.h> #include <unistd.h> int initsuspend(int signo); int restore(void); int simplesuspend(void); int main(void) { fprintf(stderr, "This is process %ld ", (long)getpid()); for ( ; ; ) { if (initsuspend(SIGUSR1)) { perror("Failed to setup handler for SIGUSR1"); return 1; } fprintf(stderr, "Waiting for signal "); if (simplesuspend()) { perror("Failed to suspend for signal"); return 1; } fprintf(stderr, "Got signal "); if (restore()) { perror("Failed to restore original handler"); return 1; } } return 1; }
Program 8.9, which is based on the strategy of Example 8.25, uses two signals to control the setting or clearing of a flag. To use the service, a program calls initnotify
with the two signals that are to be used for control. The signo1
signal handler sets the notifyflag
; the signo2
signal handler clears the notifyflag
. After the initialization, the program can call waitnotifyon
to suspend until the notification is turned on by the delivery of a signo1
signal.
Example 8.9. notifyonoff.c
An object that provides two-signal control for turning on or off a service.
#include <errno.h> #include <signal.h> #include <stdio.h> static volatile sig_atomic_t notifyflag = 1; static int signal1 = 0; static int signal2 = 0; /* ARGSUSED */ static void turnon(int s) { notifyflag = 1; } /* ARGSUSED */ static void turnoff(int s) { notifyflag = 0; } /* ---------------------------Public functions --------------------------*/ int initnotify(int signo1, int signo2) { /* set up for the notify */ struct sigaction newact; signal1 = signo1; signal2 = signo2; newact.sa_handler = turnon; /* set up signal handlers */ newact.sa_flags = 0; if ((sigemptyset(&newact.sa_mask) == -1) || (sigaddset(&newact.sa_mask, signo1) == -1) || (sigaddset(&newact.sa_mask, signo2) == -1) || (sigaction(signo1, &newact, NULL) == -1)) return -1; newact.sa_handler = turnoff; if (sigaction(signo2, &newact, NULL) == -1) return -1; return 0; } int waitnotifyon(void) { /* Suspend until notifyflag is nonzero */ sigset_t maskblocked, maskold, maskunblocked; if ((sigprocmask(SIG_SETMASK, NULL, &maskblocked) == -1) || (sigprocmask(SIG_SETMASK, NULL, &maskunblocked) == -1) || (sigaddset(&maskblocked, signal1) == -1) || (sigaddset(&maskblocked, signal2) == -1) || (sigdelset(&maskunblocked, signal1) == -1) || (sigdelset(&maskunblocked, signal2) == -1) || (sigprocmask(SIG_BLOCK, &maskblocked, &maskold) == -1)) return -1; while (notifyflag == 0) sigsuspend(&maskunblocked); if (sigprocmask(SIG_SETMASK, &maskold, NULL) == -1) return -1; return 0; }
Section 5.6 presented a simplebiff
program to notify a user when mail is present. Program 8.10 shows a more sophisticated version that uses stat
to determine when the size of the mail file increases. The program outputs the bell character to inform the user that new mail has arrived. This program uses the service of Program 8.9 to turn mail notification on or off without killing the process. The user sends a SIGUSR1
signal to turn on mail notification and a SIGUSR2
signal to turn off mail notification.
Example 8.10. biff.c
A biff program that uses the notifyonoff
service.
#include <errno.h> #include <limits.h> #include <pwd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include "notifyonoff.h" #define MAILDIR "/var/mail/" static int checkmail(char *filename) { /* is there new mail ? */ struct stat buf; int error = 0; static long newsize = 0; static long oldsize = 0; error = stat(filename, &buf); /* check the file status */ if ((error == -1) && (errno != ENOENT)) return -1; /* real error indicated by -1 return */ if (!error) newsize = (long)buf.st_size; else newsize = 0; if (newsize > oldsize) error = 1; /* return 1 to indicate new mail */ else error = 0; /* return 0 to indicate no new mail */ oldsize = newsize; return error; } int main(int argc, char *argv[]) { int check; char mailfile[PATH_MAX]; struct passwd *pw; int sleeptime; if (argc != 2) { fprintf(stderr, "Usage: %s sleeptime ", argv[0]); return 1; } sleeptime = atoi(argv[1]); if ((pw = getpwuid(getuid())) == NULL) { perror("Failed to determine login name"); return 1; } if (initnotify(SIGUSR1, SIGUSR2) == -1) { perror("Failed to set up turning on/off notification"); return 1; } snprintf(mailfile, PATH_MAX,"%s%s",MAILDIR,pw->pw_name); for( ; ; ) { waitnotifyon(); sleep(sleeptime); if ((check = checkmail(mailfile)) == -1) { perror("Failed to check mail file"); break; } if (check) fprintf(stderr, " 07"); } return 1; }
The sigwait
function blocks until any of the signals specified by *sigmask
is pending and then removes that signal from the set of pending signals and unblocks. When sigwait
returns, the number of the signal that was removed from the pending signals is stored in the location pointed to by signo
.
SYNOPSIS #include <signal.h> int sigwait(const sigset_t *restrict sigmask, int *restrict signo); POSIX:CX
If successful, sigwait
returns 0. If unsuccessful, sigwait
returns –1 and sets errno
. No mandatory errors are defined for sigwait
.
Note the differences between sigwait
and sigsuspend
. Both functions have a first parameter that is a pointer to a signal set (sigset_t *
). For sigsuspend
, this set holds the new signal mask and so the signals that are not in the set are the ones that can cause sigsuspend
to return. For sigwait
, this parameter holds the set of signals to be waited for, so the signals in the set are the ones that can cause the sigwait
to return. Unlike sigsuspend
, sigwait
does not change the process signal mask. The signals in sigmask
should be blocked before sigwait
is called.
Program 8.11 uses sigwait
to count the number of times the SIGUSR1
signal is delivered to the process. Notice that no signal handler is necessary, since the signal is always blocked.
Example 8.11. countsignals.c
A program that counts the number of SIGUSR1
signals sent to it.
#include <signal.h> #include <stdio.h> #include <unistd.h> int main(void) { int signalcount = 0; int signo; int signum = SIGUSR1; sigset_t sigset; if ((sigemptyset(&sigset) == -1) || (sigaddset(&sigset, signum) == -1) || (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1)) perror("Failed to block signals before sigwait"); fprintf(stderr, "This process has ID %ld ", (long)getpid()); for ( ; ; ) { if (sigwait(&sigset, &signo) == -1) { perror("Failed to wait using sigwait"); return 1; } signalcount++; fprintf(stderr, "Number of signals so far: %d ", signalcount); } }
Be aware of three difficulties that can occur when signals interact with function calls. The first concerns whether POSIX functions that are interrupted by signals should be restarted. Another problem occurs when signal handlers call nonreentrant functions. A third problem involves the handling of errors that use errno
.
What happens when a process catches a signal while it is executing a library function? The answer depends on the type of call. Terminal I/O can block the process for an undetermined length of time. There is no limit on how long it takes to get a key value from a keyboard or to read from a pipe. Function calls that perform such operations are sometimes characterized as “slow”. Other operations, such as disk I/O, can block for short periods of time. Still others, such as getpid
, do not block at all. Neither of these last types is considered to be “slow”.
The slow POSIX calls are the ones that are interrupted by signals. They return when a signal is caught and the signal handler returns. The interrupted function returns –1 with errno
set to EINTR
. Look in the ERRORS section of the man page to see if a given function can be interrupted by a signal. If a function sets errno
and one of the possible values is EINTR
, the function can be interrupted. The program must handle this error explicitly and restart the system call if desired. It is not always possible to logically determine which functions fit into this category, so be sure to check the man page.
It was originally thought that the operating system needs to interrupt slow calls to allow-the user the option of canceling a blocked call. This traditional treatment of handling blocked functions has been found to add unneeded complexity to many programs. The POSIX committee decided that new functions (such as those in the POSIX threads extension) would never set errno
to EINTR
. However, the behavior of traditional functions such as read
and write
was not changed. Appendix B gives a restart library of wrappers that restart common interruptible functions such as read
and write
.
Recall that a function is async-signal safe if it can be safely called from within a signal handler. Many POSIX library functions are not async-signal safe because they use static data structures, call malloc
or free
, or use global data structures in a nonreentrant way. Consequently, a single process might not correctly execute concurrent calls to these functions.
Normally this is not a problem, but signals add concurrency to a program. Since signals occur asynchronously, a process may catch a signal while it is executing a library function. (For example, suppose the program interrupts a strtok
call and executes another strtok
in the signal handler. What happens when the first call resumes?) You must therefore be careful when calling library functions from inside signal handlers. Table 8.2 lists the functions that POSIX guarantees are safe to call from a signal handler. Notice that functions such as fprintf
from the C standard I/O library are not on the list.
Signal handlers can be entered asynchronously, that is, at any time. Care must be taken so that they do not interfere with error handling in the rest of the program. Suppose a function reports an error by returning -1 and setting errno
. What happens if a signal is caught before the error message is printed? If the signal handler calls a function that changes errno
, an incorrect error might be reported. As a general rule, signal handlers should save and restore errno
if they call functions that might change errno
.
Example 8.28.
The following function can be used as a signal handler. The myhandler
saves the value of errno
on entry and restores it on return.
void myhandler(int signo) { int esaved; esaved = errno; write(STDOUT_FILENO, "Got a signal ", 13); errno = esaved; }
Table 8.2. Functions that POSIX guarantees to be async-signal safe.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Signal handling is complicated, but here are a few useful rules.
When in doubt, explicitly restart library calls within a program or use the restart library of Appendix B.
Check each library function used in a signal handler to make sure that it is on the list of async-signal safe functions.
Carefully analyze the potential interactions between a signal handler that changes an external variable and other program code that accesses the variable. Block signals to prevent unwanted interactions.
Save and restore errno
when appropriate.
Programs sometimes use signals to handle errors that are not fatal but that can occur in many places in a program. For example, a user might want to avoid terminating a program while aborting a long calculation or an I/O operation that has blocked for a long time. The program’s response to Ctrl-C should be to start over at the beginning (or at some other specified location). A similar situation occurs when the program has nested prompts or menus and should start over when a user misenters a response. Object-oriented languages often handle these situations by throwing exceptions that are caught elsewhere. C programs can use signals indirectly or directly to handle this type of problem.
In the indirect approach, the signal handler for SIGINT
sets a flag in response to Ctrl-C. The program tests the flag in strategic places and returns to the desired termination point if the flag is set. The indirect approach is complicated, since the program might have to return through several layers of functions. At each return layer, the program tests the flag for this special case.
In the direct approach, the signal handler jumps directly back to the desired termination point. The jump requires unraveling the program stack. A pair of functions, sigsetjmp
and siglongjmp
, provides this capability. The sigsetjmp
function is analogous to a statement label, and siglongjmp
function is analogous to a goto
statement. The main difference is that the sigsetjmp
and siglongjmp
pair cleans up the stack and signal states as well as doing the jump.
Call the sigsetjmp
at the point the program is to return to. The sigsetjmp
provides a marker in the program similar to a statement label. The caller must provide a buffer, env
, of type sigjmp_buf
that sigsetjmp
initializes to the collection of information needed for a jump back to that marker. If savemask
is nonzero, the current state of the signal mask is saved in the env
buffer. When the program calls sigsetjmp
directly, it returns 0. To jump back to the sigsetjmp
point from a signal handler, execute siglongjmp
with the same sigjmp_buf
variable. The call makes it appear that the program is returning from sigsetjmp
with a return value of val
.
SYNOPSIS #include <setjmp.h> void siglongjmp(sigjmp_buf env, int val); int sigsetjmp(sigjmp_buf env, int savemask); POSIX:CX
No errors are defined for siglongjmp
. The sigsetjmp
returns 0 when invoked directly and the val
parameter value when invoked by calling siglongjmp
.
The C standard library provides functions setjmp
and longjmp
for the types of jumps referred to above, but the action of these functions on the signal mask is system dependent. The sigsetjmp
function allows the program to specify whether the signal mask should be reset when a signal handler calls this function. The siglongjmp
function causes the signal mask to be restored if and only if the value of savemask
is nonzero. The val
parameter of siglongjmp
specifies the value that is to be returned at the point set by sigsetjmp
.
Example 8.12. sigjmp.c
Code to set up a signal handler that returns to the main loop when Ctrl-C is typed.
#include <setjmp.h> #include <signal.h> #include <stdio.h> #include <unistd.h> static sigjmp_buf jmpbuf; static volatile sig_atomic_t jumpok = 0; /* ARGSUSED */ static void chandler(int signo) { if (jumpok == 0) return; siglongjmp(jmpbuf, 1); } int main(void) { struct sigaction act; act.sa_flags = 0; act.sa_handler = chandler; if ((sigemptyset(&act.sa_mask) == -1) || (sigaction(SIGINT, &act, NULL) == -1)) { perror("Failed to set up SIGINT handler"); return 1; } /* stuff goes here */ fprintf(stderr, "This is process %ld ", (long)getpid()); if (sigsetjmp(jmpbuf, 1)) fprintf(stderr, "Returned to main loop due to ^c "); jumpok = 1; for ( ; ; ) ; /* main loop goes here */ }
Program 8.12 shows how to set up a SIGINT
handler that causes the program to return to the main loop when Ctrl-C is typed. It is important to execute sigsetjmp
before calling siglongjmp
in order to establish a point of return. The call to sigaction
should appear before the sigsetjmp
so that it is called only once. To prevent the signal handler from calling siglongjmp
before the program executes sigsetjmp
, Program 8.12 uses the flag jumpok
. The signal handler tests this flag before calling siglongjmp
.
Normally, when performing a read or write, a process blocks until the I/O completes. Some types of performance-critical applications would rather initiate the request and continue executing, allowing the I/O operation to be processed asynchronously with program execution. The older method of asynchronous I/O uses either SIGPOLL
or SIGIO
to notify a process when I/O is available. The mechanism for using these signals is set up with ioctl
. This section discusses the newer version which is part of the POSIX:AIO Asynchronous I/O Extension that was introduced with the POSIX:RTS Realtime Extension.
The POSIX:AIO Extension bases its definition of asynchronous I/O on four main functions. The aio_read
function allows a process to queue a request for reading on an open file descriptor. The aio_write
function queues requests for writing. The aio_return
function returns the status of an asynchronous I/O operation after it completes, and the aio_error
function returns the error status. A fifth function, aio_cancel
, allows cancellation of asynchronous I/O operations that are already in progress.
The aio_read
and aio_write
functions take a single parameter, aiocbp
, which is a pointer to an asynchronous I/O control block. The aio_read
function reads aiocbp->aio_bytes
from the file associated with aiocbp->aio_fildes
into the buffer specified by aiocbp->aio_buf
. The function returns when the request is queued. The aio_write
function behaves similarly.
SYNOPSIS #include <aio.h> int aio_read(struct aiocb *aiocbp); int aio_write(struct aiocb *aiocbp); POSIX:AIO
If the request was successfully queued, aio_read
and aio_write
return 0. If unsuccessful, these functions return –1 and set errno
. The following table lists the mandatory errors for these functions that are specific to asynchronous I/O.
| cause |
---|---|
| system did not have the resources to queue request (B) |
|
|
|
|
| request canceled because of explicit |
| invalid member of |
|
|
Errors that occur before the return of aio_read
or aio_write
have a B tag. These are values that errno
can have if the call returns –1. The errors that may occur after the return have an A tag. These errors are returned by a subsequent call to aio_error
. The aio_read
and aio_write
functions also have the mandatory errors of their respective read
and write
counterparts.
The struct aiocb
structure has at least the following members.
int aio_fildes; /* file descriptor */ volatile void *aio_buf; /* buffer location */ size_t aio_nbytes; /* length of transfer */ off_t aio_offset; /* file offset */ int aio_reqprio; /* request priority offset */ struct sigevent aio_sigevent; /* signal number and value */ int aio_lio_opcode; /* listio operation */
The first three members of this structure are similar to the parameters in an ordinary read
or write
function. The aio_offset
specifies the starting position in the file for the I/O. If the implementation supports user scheduling (_POSIX_PRIORITIZED_IO
and _POSIX_PRIORITY_SCHEDULING
are defined), aio_reqprio
lowers the priority of the request. The aio_sigevent
field specifies how the calling process is notified of the completion. If aio_sigevent.sigev_notify
has the value SIGEV_NONE
, the operating system does not generate a signal when the I/O completes. If aio_sigevent.sigev_notify
is SIGEV_SIGNAL
, the operating system generates the signal specified in aio_sigevent.sigev_signo
. The aio_lio_opcode
function is used by the lio_listio
function (not discussed here) to submit multiple I/O requests.
The aio_error
and aio_return
functions return the status of the I/O operation designated by aiocbp
. Monitor the progress of the asynchronous I/O operation with aio_error
. When the operation completes, call aio_return
to retrieve the number of bytes read or written.
SYNOPSIS #include <aio.h> ssize_t aio_return(struct aiocb *aiocbp); int aio_error(const struct aiocb *aiocbp); POSIX:AIO
The aio_error
function returns 0 when the I/O operation has completed successfully or EINPROGRESS
if the I/O operation is still executing. If the operation fails, aio_error
returns the error code associated with the failure. This error status corresponds to the value of errno
that would have been set by the corresponding read
or write
function. The aio_return
function returns the status of a completed underlying I/O operation. If the operation was successful, the return value is the number of bytes read or written. Once aio_return
has been called, neither aio_return
nor aio_error
should be called for the same struct aiocb
until another asynchronous operation is started with this buffer. The results of aio_return
are undefined if the asynchronous I/O has not yet completed.
POSIX asynchronous I/O can be used either with or without signals, depending on the setting of the sigev_notify
field of the struct aiocb
. Programs 8.13 and 8.14 illustrate how to do asynchronous I/O with signals. The general idea is to set up a signal handler that does all the work after the initial I/O operation is started.
Program 8.13 is a program for copying one file to another. The reading from the first file is done with asynchronous I/O, and the writing to the second file is done with ordinary I/O. This approach is appropriate if the input is from a pipe or a network connection that might block for long periods of time and if the output is to an ordinary file. Program 8.13 takes two filenames as command-line arguments and opens the first for reading and the second for writing. The program then calls the initsignal
function to set up a signal handler and initread
to start the first read. The signal is set up as a realtime signal as described in Section 9.4. The main
program’s loop calls dowork
and checks to see if the asynchronous copy has completed with a call to getdone
. When the copying is done, the program displays the number of bytes copied or an error message.
Program 8.14 contains the signal handler for the asynchronous I/O as well as initialization routines. The initread
function sets up a struct aiocb
structure for reading asynchronously and saves the output file descriptor in a global variable. It initializes three additional global variables and starts the first read with a call to readstart
.
Program 8.14 keeps track of the first error that occurs in globalerror
and the total number of bytes transferred in totalbytes
. A doneflag
has type sig_atomic_t
so that it can be accessed atomically. This is necessary since it is modified asynchronously by the signal handler and can be read from the main
program with a call to getdone
. The variables globalerror
and totalbytes
are only available after the I/O is complete, so they are never accessed concurrently by the signal handler and the main
program.
The signal handler in Program 8.14 uses the struct aiocb
that is stored in the global variable aiocb
. The signal handler starts by saving errno
so that it can be restored when the handler returns. If the handler detects an error, it calls seterror
to store errno
in the variable globalerror
, provided that this was the first error detected. The signal handler sets the doneflag
if an error occurs or end-of-file is detected. Otherwise, the signal handler does a write to the output file descriptor and starts the next read.
Example 8.13. asyncsignalmain.c
A main
program that uses asynchronous I/O with signals to copy a file while doing other work.
#include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include "asyncmonitorsignal.h" #define BLKSIZE 1024 #define MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) void dowork(void); int main(int argc, char *argv[]) { char buf[BLKSIZE]; int done = 0; int error; int fd1; int fd2; /* open the file descriptors for I/O */ if (argc != 3) { fprintf(stderr, "Usage: %s filename1 filename2 ", argv[0]); return 1; } if ((fd1 = open(argv[1], O_RDONLY)) == -1) { fprintf(stderr, "Failed to open %s:%s ", argv[1], strerror(errno)); return 1; } if ((fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, MODE)) == -1) { fprintf(stderr, "Failed to open %s: %s ", argv[2], strerror(errno)); return 1; } if (initsignal(SIGRTMAX) == -1) { perror("Failed to initialize signal"); return 1; } if (initread(fd1, fd2, SIGRTMAX, buf, BLKSIZE) == -1) { perror("Failed to initate the first read"); return 1; } for ( ; ; ) { dowork(); if (!done) if (done = getdone()) if (error = geterror()) fprintf(stderr, "Failed to copy file:%s ", strerror(error)); else fprintf(stderr, "Copy successful, %d bytes ", getbytes()); } }
Example 8.14. asyncmonitorsignal.c
Utility functions for handling asynchronous I/O with signals.
#include <aio.h> #include <errno.h> #include <signal.h> #include "restart.h" static struct aiocb aiocb; static sig_atomic_t doneflag; static int fdout; static int globalerror; static int totalbytes; static int readstart(); static void seterror(int error); /* ARGSUSED */ static void aiohandler(int signo, siginfo_t *info, void *context) { int myerrno; int mystatus; int serrno; serrno = errno; myerrno = aio_error(&aiocb); if (myerrno == EINPROGRESS) { errno = serrno; return; } if (myerrno) { seterror(myerrno); errno = serrno; return; } mystatus = aio_return(&aiocb); totalbytes += mystatus; if (mystatus == 0) doneflag = 1; else if (r_write(fdout, (char *)aiocb.aio_buf, mystatus) == -1) seterror(errno); else if (readstart() == -1) seterror(errno); errno = serrno; } static int readstart() { /* start an asynchronous read */ int error; if (error = aio_read(&aiocb)) seterror(errno); return error; } static void seterror(int error) { /* update globalerror if zero */ if (!globalerror) globalerror = error; doneflag = 1; } /* --------------------------Public Functions ---------------------------- */ int getbytes() { if (doneflag) return totalbytes; errno = EINVAL; return -1; } int getdone() { /* check for done */ return doneflag; } int geterror() { /* return the globalerror value if doneflag */ if (doneflag) return globalerror; errno = EINVAL; return errno; } int initread(int fdread, int fdwrite, int signo, char *buf, int bufsize) { aiocb.aio_fildes = fdread; /* set up structure */ aiocb.aio_offset = 0; aiocb.aio_buf = (void *)buf; aiocb.aio_nbytes = bufsize; aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL; aiocb.aio_sigevent.sigev_signo = signo; aiocb.aio_sigevent.sigev_value.sival_ptr = &aiocb; fdout = fdwrite; doneflag = 0; globalerror = 0; totalbytes = 0; return readstart(); /* start first read */ } int initsignal(int signo) { /* set up the handler for the async I/O */ struct sigaction newact; newact.sa_sigaction = aiohandler; newact.sa_flags = SA_SIGINFO; if ((sigemptyset(&newact.sa_mask) == -1) || (sigaction(signo, &newact, NULL) == -1)) return -1; return 0; } int suspenduntilmaybeready() { /* return 1 if done, 0 otherwise */ const struct aiocb *aiocblist; aiocblist = &aiocb; aio_suspend(&aiocblist, 1, NULL); return doneflag; }
The r_write
function from the restart library in Appendix B guarantees that all the bytes requested are written if possible. Program 8.14 also contains the suspenduntilmaybeready
function, which is not used in Program 8.13 but will be described later.
The signal handler does not output any error messages. Output from an asynchronous signal handler can interfere with I/O operations in the main
program, and the standard library routines such as fprintf
and perror
may not be safe to use in signal handlers. Instead, the signal handler just keeps track of the errno
value of the first error that occurred. The main
program can then print an error message, using strerror
.
Example 8.29.
The following command line calls Program 8.13 to copy from pipe1
to pipe2
.
asyncsignalmain pipe1 pipe2
Asynchronous I/O can be used without signals if the application has to do other work that can be broken into small pieces. After each piece of work, the program calls aio_error
to see if the I/O operation has completed and handles the result if it has. This procedure is called polling.
Program 8.15 shows a main
program that takes a number of filenames as parameters. The program reads each file, using asynchronous I/O, and calls processbuffer
to process each input. While this is going on, the program calls dowork
in a loop.
Program 8.15 uses utility functions from Program 8.16. The main
program starts by opening each file and calling initaio
to set up the appropriate information for each descriptor as an entry in the static array defined in Program 8.16. Each element of the array contains a struct aiocb
structure for holding I/O and control information. Next, the first read for each file is started with a call to readstart
. The program does not use signal handlers. The main
program executes a loop in which it calls readcheck
to check the status of each operation after each piece of dowork
. If a read has completed, the main
program calls processbuffer
to handle the bytes read and starts a new asynchronous read operation. The main
program keeps track of which file reads have completed (either successfully or due to an error) in an array called done
.
Example 8.15. asyncpollmain.c
A main
program that uses polling with asynchronous I/O to process input from multiple file descriptors while doing other work.
#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include "asyncmonitorpoll.h" void dowork(void); void processbuffer(int which, char *buf, int bufsize); int main(int argc, char *argv[]) { char *buf; int done[NUMOPS]; int fd[NUMOPS]; int i; int numbytes, numfiles; if (argc < 2) { fprintf(stderr, "Usage: %s filename1 filename2 ... ", argv[0]); return 1; } else if (argc > NUMOPS + 1) { fprintf(stderr, "%s: only supports %d simultaneous operations ", argv[0], NUMOPS); return 1; } numfiles = argc - 1; for (i = 0; i < numfiles; i++) { /* set up the I/O operations */ done[i] = 0; if ((fd[i] = open(argv[i+1], O_RDONLY)) == -1) { fprintf(stderr, "Failed to open %s:%s ", argv[i+1], strerror(errno)); return 1; } if (initaio(fd[i], i) == -1) { fprintf(stderr, "Failed to setup I/O op %d:%s ", i, strerror(errno)); return 1; } if (readstart(i) == -1) { fprintf(stderr, "Failed to start read %d:%s ", i, strerror(errno)); return 1; } } for ( ; ; ) { /* loop and poll */ dowork(); for (i = 0; i < numfiles; i++) { if (done[i]) continue; numbytes = readcheck(i, &buf); if ((numbytes == -1) && (errno == EINPROGRESS)) continue; if (numbytes <= 0) { if (numbytes == 0) fprintf(stderr, "End of file on %d ", i); else fprintf(stderr, "Failed to read %d:%s ", i, strerror(errno)); done[i] = 1; continue; } processbuffer(i, buf, numbytes); reinit(i); if (readstart(i) == -1) { fprintf(stderr, "Failed to start read %d:%s ", i, strerror(errno)); done[i] = 1; } } } }
Example 8.16. asyncmonitorpoll.c
Utility functions for handling asynchronous I/O with polling.
#include <aio.h> #include <errno.h> #include <stdio.h> #include <unistd.h> #include "asyncmonitorpoll.h" #define BLKSIZE 1024 /* size of blocks to be read */ typedef struct { char buf[BLKSIZE]; ssize_t bytes; struct aiocb control; int doneflag; int startedflag; } aio_t; static aio_t iops[NUMOPS]; /* information for the op */ /* -------------------------- Public Functions ----------------------------- */ int initaio(int fd, int handle) { /* set up control structure */ if (handle >= NUMOPS) { errno = EINVAL; return -1; } iops[handle].control.aio_fildes = fd; /* I/O operation on fd */ iops[handle].control.aio_offset = 0; iops[handle].control.aio_buf = (void *)iops[handle].buf; iops[handle].control.aio_nbytes = BLKSIZE; iops[handle].control.aio_sigevent.sigev_notify = SIGEV_NONE; iops[handle].doneflag = 0; iops[handle].startedflag = 0; iops[handle].bytes = 0; return 0; } /* return -1 if not done or error errno = EINPROGRESS if not done otherwise, return number of bytes read with *buf pointing to buffer */ int readcheck(int handle, char **bufp) { /* see if read for handle is done */ int error; ssize_t numbytes; struct aiocb *thisp; thisp = &(iops[handle].control); /* get a pointer to the aiocp */ if (iops[handle].doneflag) { /* done already, don't call aio_return */ numbytes = iops[handle].bytes; *bufp = (char *)iops[handle].control.aio_buf; /* set pointer to buffer */ return numbytes; } error = aio_error(thisp); if (error) { errno = error; return -1; } numbytes = aio_return(thisp); iops[handle].bytes = numbytes; *bufp = (char *)iops[handle].control.aio_buf; /* set pointer to buffer */ iops[handle].doneflag = 1; return numbytes; } int readstart(int handle) { /* start read for I/O corresponding to handle */ int error; struct aiocb *thisp; thisp = &(iops[handle].control); /* get a pointer to the aiocp */ if (iops[handle].startedflag) { /* already started */ errno = EINVAL; return -1; } if ((error = aio_read(thisp)) == -1) { errno = error; return -1; } iops[handle].startedflag = 1; return 0; } void reinit(int handle) { /* must be called before doing another readstart */ iops[handle].doneflag = 0; iops[handle].startedflag = 0; iops[handle].bytes = 0; }
Example 8.30.
The following command line calls Program 8.15 for inputs pipe1
, pipe2
and pipe3
.
asyncpollmain pipe1 pipe2 pipe3
What if a program starts asynchronous I/O operations as in Program 8.13 and runs out of other work to do? Here are several options for avoiding busy waiting.
Switch to using standard blocking I/O with select
.
Use signals as in Program 8.13, or use pause
or sigsuspend
in a loop. Do not use sigwait
, since this function requires the signals to be blocked.
Switch to using signals as in Program 8.15 by blocking the signal and calling sigwait
in a loop.
Use aio_suspend
.
The aio_suspend
function takes three parameters, an array of pointers to struct aiocb
structures, the number of these structures and a timeout specification. If the timeout specification is not NULL
, aio_suspend
may return after the specified time. Otherwise, it returns when at least one of the I/O operations has completed and aio_error
no longer returns EINPROGRESS
. Any of the entries in the array may be NULL
, in which case they are ignored.
SYNOPSIS #include <aio.h> int aio_suspend(const struct aiocb * const list[], int nent, const struct timespec *timeout); POSIX:AIO
If successful, aio_suspend
returns 0. If unsuccessful, aio_suspend
returns –1 and sets errno
. The following table lists the mandatory errors for aio_suspend
.
| cause |
---|---|
| timeout occurred before asynchronous I/O completed |
| a signal interrupted |
Program 8.14 has a suspenduntilmaybeready
function that uses aio_suspend
to suspend the calling process until the asynchronous I/O operation is ready. It can be called from the main
program of Program 8.13 in place of dowork
when there is no other work to be done. In this case, there is only one asynchronous I/O operation and the function returns 1 if it has completed, and 0 otherwise.
The aio_cancel
function attempts to cancel one or more asynchronous I/O requests on the file descriptor fildes
. The aiocbp
parameter points to the control block for the request to be canceled. If aiocbp
is NULL
, the aio_cancel
function attempts to cancel all pending requests on fildes
.
SYNOPSIS #include <aio.h> int aio_cancel(int fildes, struct aioch *aiocbp); POSIX:AIO
The aio_cancel
function returns AIO_CANCELED
if the requested operations were successfully canceled or AIO_NOTCANCELED
if at least one of the requested operations could not be canceled because it was in progress. It returns AIO_ALLDONE
if all the operations have already completed. Otherwise, the aio_cancel
function returns –1 and sets errno
. The aio_cancel
function sets errno
to EBADF
if the fildes
parameter does not correspond to a valid file descriptor.
Example 8.31.
How would you modify Programs 8.15 and 8.16 so that a SIGUSR1
signal cancels all the asynchronous I/O operations without affecting the rest of the program?
Answer:
Set up a signal handler for SIGUSR1
in asyncmonitorpoll
that cancels all pending operations using aio_cancel
. Also set a flag signifying that all I/O has been canceled. The readcheck
function checks this flag. If the flag is set, readcheck
returns –1 with errno
set to ECANCELED
.
The atexit
function showtimes
of Program 2.10 on page 53 can almost work as a signal handler to report the amount of CPU time used. It needs an unused parameter for the signal number, and the functions used in showtimes
must be async-signal safe. Implement a signal handler for SIGUSR1
that outputs this information to standard error. The program probably produces correct output most of the time, even though it calls functions such as perror
and fprintf
that are not async-signal safe.
Read your system documentation and try to find out if these functions are async-signal safe on your system. This information may be difficult to find. If you are using unsafe functions, try to make your program fail. This may not be easy to do, as it may happen very rarely. In any case, write a version that uses only those functions that POSIX requires to be async-signal safe as listed in Table 8.2 on page 285. You can avoid using perror
by producing your own error messages. You will need to write your own functions for converting a double value to a string. Section 13.7 gives a signal-safe implementation of perror
that uses mutex locks from the POSIX:THR Threads Extension.
This exercise uses asynchronous I/O to overlap the handling of I/O from a slow device with other program calculations. Examples include printing or performing a file transfer over a slow modem. Another example is a program that plays an audio file in the background while doing something else. In these examples, a program reads from a disk file and writes to a slow device.
Write a program that uses aio_read
and aio_write
to transfer data to a slow device. The source of information is a disk file. Model your program after Programs 8.13 and 8.14. Pass the name of the input and output files as command-line arguments.
The main
program still initiates the first read. However, now the signal handler initiates an aio_write
if the asynchronous read completes. Similarly, when the asynchronous write completes, the signal handler initiates another aio_read
.
Begin testing with two named pipes for the input and the output. Then, use a disk file for the output. Redirect the output from the pipe to a file and use diff
to check that they are the same. If a workstation with a supported audio device is available, use an audio file on disk as input and "/dev/audio"
as the output device.
Keep statistics on the number of bytes transferred and the number of write operations needed. Add a signal handler that outputs this information when the program receives a SIGUSR1
signal. The statistics can be kept in global variables. Block signals when necessary to prevent different signal handlers from accessing these shared variables concurrently.
This program is particularly interesting when the output goes to the audio device. It is possible to tell when the program is computing by the gaps that occur in the audio output. Estimate the percentage of time spent handling I/O as compared with calculation time.
Advanced Programming in the UNIX Environment by Stevens [112] has a good historical overview of signals. Beginning Linux Programming, 2nd ed. by Stones and Matthew discusses signals in Linux [117]. The article “Specialization tools and techniques for systematic optimization of system software” by McNamee et al. [80] introduces a toolkit for writing efficient system code and uses signal handling as a principal case study for the toolkit.