System Interface Guide

Chapter 4 Asynchronous Notification

This chapter describes asynchronous notification mechanisms. These are signals, which come in two flavors, and polling, which comes in three flavors. We describe what they can do for an application and what they can do to an application.

Signals

Signals are software generated interrupts that are sent to a process when an event happens. Signals can be synchronously generated by an error in an application, such as SIGFPE and SIGSEGV, but most signals are asynchronous. Signals can be posted to a process when the system detects a software event, such as a user entering an interrupt or stop or a kill request from another process. Signals can be sent by the kernel when a hardware event such as a bus error or an illegal instruction is encountered. Signals can be sent by the kernel to note the completion of an input/output request.

The system defines a set of signals that can be posted to a process. Signal delivery is analogous to hardware interrupts in that a signal can be blocked from being delivered in the future. Most signals cause termination of the receiving process if no action is taken by the process in response to the signal. Some signals stop the receiving process and other signals can be ignored. Each signal has a default action that is one of the following:

Each signal defined by the system falls into one of five classes:

The set of signals is defined in the header <signal.h>.

Signal Processing

When a signal is delivered to a process, it is added to a set of signals pending for the process. If the signal is not blocked for the process, it is delivered. When a signal is delivered, the current state of the process is saved, a new signal mask is calculated, and the signal handler is invoked.

In BSD signal semantics, when a signal is delivered to a process, a new signal mask is installed for the duration of the process's signal handler (or until a mask modifying interface is called). This mask blocks the signal that has just been delivered from interrupting the process again while the handler for the signal is running.

System V signal semantics do not provide this protection, letting the same signal interrupt the handler that processes the signal. This requires that a signal handler be reentrant.

All signals have the same priority. If a signal handler blocks the signal that invoked it, other signals can still be delivered to the process.

Signals are not stacked. There is no way for a signal handler to record how many times a signal has actually been delivered.

Blocking

A global signal mask defines the set of signals that are blocked from being delivered to a process. A process's signal mask copies its initial state from the signal mask of the parent process. The mask can be changed with calls to sigaction(2), sigblock(3UCB), sigsetmask(3UCB), sigprocmask(2), sigsetops(3C), sighold(3C), or sigrelse(3C) (see signal(3C)).

Handling

An application program can specify a function called a signal handler to be invoked when a specific signal is received. When a signal handler is invoked on receipt of a signal, it is said to catch the signal. A process can deal with a signal in one of the following ways:

Signal handlers usually execute on the current stack of the process. This lets the signal handler return to the point that execution was interrupted in the process. This can be changed on a per-signal basis so that a signal handler executes on a special stack. If a process must resume in a different context than the interrupted one, it must restore the previous context itself.

Installing a Handler

The functions signal(3C), sigset(3C), signal(3UCB), and sigvec(3UCB) can all be used to install a signal handler. All return the previous action for the signal. There is one major difference between the four interfaces: signal(3C) results in System V signal semantics (the same signal can interrupt its handler); sigset(3C), signal(3UCB), and sigvec(3UCB) all result in BSD signal semantics (the signal is blocked until its handler returns). In addition, both signal(3C) and signal(3UCB) can flag that the signal is to be ignored or that the default action be restored. Example 4-1 illustrates a simple handler and its installation.


Example 4-1 Signal handler installation

#include <stdio.h>
#include <signal.h>
#define TRUE 1

void sigcatcher()
{
		printf ("PID %d caught signal.\n", getpid());
}

main()
{
		pid_t ppid;

		signal (SIGINT, sigcatcher);
		if (fork() == 0) {
			sleep( 5 );
			ppid = getppid();
			while( TRUE )
				if (kill( ppid, SIGINT) == -1 )
					exit( 1 );
		}
		pause();
}

Trying to install a handler or set SIG_IGN for the signals SIGKILL or SIGSTOP results in an error. Trying to set SIG_IGN for the signal SIGCONT also results in an error, since it is ignored by default.

After a signal handler is installed, it remains so until it is explicitly replaced by another call to signal(3C), sigset(3C), signal(3UCB), or sigvec(3UCB).

Catching SIGCHLD

When a child process stops or terminates, SIGCHLD is sent to the parent process. The default response to the signal is to ignore it. The signal can be caught and the exit status from the child process can be obtained by immediately calling wait(2) and wait3(3C). This allows zombie process entries to be removed as quickly as possible. Example 4-2 demonstrates installing a handler that catches SIGCHLD.


Example 4-2 Catching SIGCHLD

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/resource.h>

void proc_exit()
{
		int wstat;
		union wait wstat;
		pid_t	pid;

		while (TRUE) {
			pid = wait3 (&wstat, WNOHANG, (struct rusage *)NULL );
			if (pid == 0)
				return;
			else if (pid == -1)
				return;
			else
				printf ("Return code: %d\n", wstat.w_retcode);
		}
}
main ()
{
		signal (SIGCHLD, proc_exit);
		switch (fork()) {
			case -1:
				perror ("main: fork");
				exit (0);
			case 0:
				printf ("I'm alive (temporarily)\n");
				exit (rand());
			default:
				pause();
		}
}

SIGCHLD catchers are usually set up as part of process initialization. They must be set before a child process is forked. A typical SIGCHLD handler retrieves the child process's exit status.