Chapter 8. Signals

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.

Basic Signal Concepts

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.

Generating Signals

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

SIGABRT

process abort

implementation dependent

SIGALRM

alarm clock

abnormal termination

SIGBUS

access undefined part of memory object

implementation dependent

SIGCHLD

child terminated, stopped or continued

ignore

SIGCONT

execution continued if stopped

continue

SIGFPE

error in arithmetic operation as in division by zero

implementation dependent

SIGHUP

hang-up (death) on controlling terminal (process)

abnormal termination

SIGILL

invalid hardware instruction

implementation dependent

SIGINT

interactive attention signal (usually Ctrl-C)

abnormal termination

SIGKILL

terminated (cannot be caught or ignored)

abnormal termination

SIGPIPE

write on a pipe with no readers

abnormal termination

SIGQUIT

interactive termination: core dump (usually Ctrl-|)

implementation dependent

SIGSEGV

invalid memory reference

implementation dependent

SIGSTOP

execution stopped (cannot be caught or ignored)

stop

SIGTERM

termination

abnormal termination

SIGTSTP

terminal stop

stop

SIGTTIN

background process attempting to read

stop

SIGTTOU

background process attempting to write

stop

SIGURG

high bandwidth data available at a socket

ignore

SIGUSR1

user-defined signal 1

abnormal termination

SIGUSR2

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.2. 

The following command sends the SIGUSR1 signal to process 3423.

kill -s USR1 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

EINVAL

sig is an invalid or unsupported signal

EPERM

caller does not have the appropriate privileges

ESRCH

no process or process group corresponds to pid

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.

Example 8.8. simplealarm.c

Since the default action for SIGALRM is to terminate the process, the following program runs for approximately ten seconds of wall-clock time.

#include <unistd.h>

int main(void) {
   alarm(10);
   for ( ; ; ) ;
}

Manipulating Signal Masks and Signal Sets

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.

SIG_BLOCK:

add a collection of signals to those currently blocked

SIG_UNBLOCK:

delete a collection of signals from those currently blocked

SIG_SETMASK:

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.

Catching and Ignoring Signals—sigaction

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.

errno

cause

EINVAL

sig is an invalid signal number, or attempt to catch a signal that cannot be caught, or attempt to ignore a signal that cannot be ignored

ENOTSUP

SA_SIGINFO bit of the sa_flags is set and the implementation does not support POSIX:RTS or POSIX:XSI

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.

Waiting for Signals—pause, sigsuspend and sigwait

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

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 sigsuspend function

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

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);
    }
}

Handling Signals: Errors and Async-signal Safety

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.

_Exit

getpid

sigaddset

_exit

getppid

sigdelset

accept

getsockname

sigemptyset

access

getsockopt

sigfillset

aio_error

getuid

sigismember

aio_return

kill

signal

aio_suspend

link

sigpause

alarm

listen

sigpending

bind

lseek

sigprocmask

cfgetispeed

lstat

sigqueue

cfgetospeed

mkdir

sigset

cfsetispeed

mkfifo

sigsuspend

cfsetospeed

open

sleep

chdir

pathconf

socket

chmod

pause

socketpair

chown

pipe

stat

clock_gettime

poll

symlink

close

posix_trace_event

sysconf

connect

pselect

tcdrain

creat

raise

tcflow

dup

read

tcflush

dup2

readlink

tcgetattr

execle

recv

tcgetpgrp

execve

recvfrom

tcsendbreak

fchmod

recvmsg

tcsetattr

fchown

rename

tcsetpgrp

fcntl

rmdir

time

fdatasync

select

timer_getoverrun

fork

sem_post

timer_gettime

fpathconf

send

timer_settime

fstat

sendmsg

times

fsync

sendto

umask

ftruncate

setgid

uname

getegid

setpgid

unlink

geteuid

setsid

utime

getgid

setsockopt

wait

getgroups

setuid

waitpid

getpeername

shutdown

write

getpgrp

sigaction

 

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.

Program Control with siglongjmp and sigsetjmp

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.

Programming with Asynchronous I/O

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.

errno

cause

EAGAIN

system did not have the resources to queue request (B)

EBADF

aiocbp->aio_fildes invalid (BA)

EFBIG

aiocbp->aio_offset exceeds maximum (aio_write) (BA)

ECANCELED

request canceled because of explicit aio_cancel (A)

EINVAL

invalid member of aiocbp (BA)

EOVERFLOW

aiocbp->aio_offset exceeds maximum (aio_read) (BA)

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.

  1. Switch to using standard blocking I/O with select.

  2. 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.

  3. Switch to using signals as in Program 8.15 by blocking the signal and calling sigwait in a loop.

  4. 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.

errno

cause

EAGAIN

timeout occurred before asynchronous I/O completed

EINTR

a signal interrupted aio_suspend

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.

Exercise: Dumping Statistics

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.

Exercise: Spooling a Slow Device

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.

Additional Reading

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset