COP4610: Operating Systems & Concurrent Programming up ↑

Unix Signals

 

These notes are intended only to provide a brief introduction to signals. They are not intended to fully duplicate the on-line manual pages. For detailed explanations of signals, handlers, and the operations that generate signals, use the Unix man command to view the on-line manual pages, or look in a book about Unix.

Signals

Unix operating systems support the notion of signals. A signal may be generated when an event occurs, and is identified by a number. Different signal numbers are associated with different kinds of events. For example, the signal SIGALRM is generated for a process when the time delay requested by a prior call to alarm() has expired.

Signals behave similarly to hardware interrupts. There are system calls that a thread can use to selective ignore certain signals, or to defer the effect of a signal. The latter is called masking or blocking the signal. Instances of the signal that are generated while the signal is blocked have no effect until the signal is unblocked. However, while the signal is blocked only one instance of the signal is required to be "remembered" by the system. In general, the process or thread will not be able to be certain whether the signal was generated just once or many times while it was blocked.

The exact set of signals supported varies between different Unix implementations, but certain named signals are guaranteed to exist. These include, for example, the following:

SIGALRM
The timer alarm signal, can be blocked and handled
SIGUSR1, SIGUSR2
Are available for application use, can be blocked and handled
SIGKILL
Always kills the process, can never be blocked or handled

The kill() System Call

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

The kill() system call is used to send a signal to a process.

Handlers

A process may attach a handler subprogram to certain signals. If a handler is attached to a given signal, whenever the signal occurs and is not blocked the handler procedure will execute, interrupting the execution of one of the threads in the process. The system passes the number of the signal to the handler, which can be useful if the same handler is installed for more than one signal.

The sigaction() System Call

#include <signal.h>
int sigaction(int signum,  const  struct  sigaction  *act,  struct
       sigaction *oldact);

The sigaction system call is used to attach a handle to a signal, or to otherwise modify the action associated with the signal. The signal action is specified by a value of the type struct sigaction. One way to use sigaction is to call it once to fetch the action that is currently associated with the signal, make changes to that value, and then call sigaction again to install the new action.

The alarm() System Call

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

The Unix alarm() system call specifies a number of seconds after which the calling process should receive an instance of the SIGALRM signal. The signal is generated the specified number of seconds after the time the alarm() call is executed.

This can be used for error recovery, to arrange a "timeout" for an operation that the application expects might take too long, such as a potentially infinite loop, or a system call that might block forever.

If the application does not install a handler for SIGALRM, the timeout will kill the process abruptly. That may not be good if the application was doing anything that cannot safely be left incomplete. For example, the process might be holding locks, or might have buffered output.

Therefore, an application that uses alarm() will usually use sigaction() to install a handler for SIGALRM, which allows the process to recover or to exit gracefully.

The sigsuspend() System Call

#include <signal.h>
int sigsuspend(const sigset_t *mask);

The sigsuspend() function blocks the calling thread until it is interrupted by a signal. It can be used in process synchronization, to wait for an event that is signaled by another process. Since the default action of most signals is to kill the process, it is important for the process to have a signal handler installed for a given signal before it waits for that signal.

The parameter specifies a new signal mask that is intalled atomically with the blocking of the thread. This is important so as to not miss signals.

See the program examples/philos/chopsticks2.c for a complete example of how this operation can be used.

The sigset_t Data Type

The sigset_t data type is used to represent a set of signals. The API for this type is as follows:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);

The sigprocmask() System Call

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

The sigprocmask() function is used to modify the signal mask of the calling process. It is not supposed to be used with multithreaded processes. The corresponding operation for threads is pthread_sigmask().

The operation takes two signal sets as parameters, so that it is possible to atomically fetch the old signal mask and install a new signal mask. There are options to add in a set of signals to the process signal mask (SIG_BLOCK), delete a set of signals from the process signal mask (SIG_UNBLOCK), or install an entirely new mask (SIG_SETMASK).

The sigpending() System Call

#include <signal.h>
int sigpending(sigset_t *set);

The sigpending() function can be used to find out what signals are currently pending on the current thread and process.

T. P. Baker. ($Id)