Signals

Signals are an important IPC mechanism that are used to communicate events to processes. As we discussed in the previous module, signals can be generated by the kernel, such as the SIGSEGV (segmentation fault) signal that is generated when a process performs an illegal memory access. However, processes can also send signals to each other!

The C standards define a very limited framework for signal handling, which makes sense because C was intended to be portable to many system which may have very different conceptions of how signals function. For example, on an embedded system, signals might actually be implemented as processor interrupts–installing a signal handler would imply writing a interrupt vector (function pointer) into the interrupt vector table for the CPU. On the other hand, modern POSIX-based operating systems provide a much richer signal framework.

POSIX defines a large set of signals, each associated with a particular event. Many signals are used to indicate errors. For example, the operating system generates and sends several signals to processes that perform illegal actions such as:

SIGBUS–Bus error (bad memory access)

An attempt is made to access an invalid memory address.

SIGFPE–Floating-point exception

An llegal floating-point operation is attempted, such as division-by-zero.

SIGILL–Illegal instruction

An attempt is made to execute an invalid instruction, such as an invalid opcode or argument.

SIGSEGV–Invalid memory reference

An attempt is made to access a valid memory address, but permission is denied.

SIGSYS–Bad system call

An attempt is made to execute a system call that does not exist.

Most signals, including those listed above, cause process termination, which may also dump the process core for post-mortem debugging. Processes can however catch and handle, temporarily block, or ignore most signals. The only two signals processes cannot catch, block, or ignore are the SIGKILL and SIGSTOP signals which forcibly kill or stop a running process. There are also a few restrictions on handling certain types of signals such as SIGSEGV–while SIGSEGV can be caught, returning from the signal handler would result in undefined behavior due to the attempted memory access having failed.

In addition to error-indicating signals, POSIX provides several other signals that notify processes of important events that aren’t necessarily errors. For example,

SIGHUP–Hangup detected on controlling terminal or death of controlling process

The hangup signal normally terminates running processes so that they are not left running after the terminal is disconnected. Some processes may wish to continue running, and ignore, block, or catch the SIGHUP signal.

SIGINT–Interrupt from keyboard

The interrupt signal normally terminates a process, to return control of the terminal to the user’s shell. Processes might instead choose to handle the interrupt signal differently. For example, the shell handles the SIGINT signal so that it can be used to break out of an infinite loop in the shell, rather than terminating the shell.

SIGPIPE–Broken pipe: write to pipe with no readers

The broken pipe signal normally terminates a process. When a process writes to a pipe with no reader, it’s likely that the pipe–which has limited space–will fill up and the process will hang indefinitely. This makes terminating a good default action, but allows processes to handle this case if they choose to, without terminating.

SIGQUIT–Quit from keyboard

Like SIGINT, this normally terminates a process to return control of the terminal to the user’s shell. Processes might intercept this signal to perform cleanup operations.

SIGUSR1 and SIGUSR2–User-defined signal

These are signals which have no defined meaning and can be used to signify anything to a process that wants to catch them; if such a signal is caught by a process, it is usually described in the documentation. For example, xargs can be used to run a specified command many times with different arguments, in parallel. While xargs is running, it interprets the SIGUSR1 and SIGUSR2 to increase or decrease the number of parallel processes to use.

Many of these signals are generated by other processes, or by the user, and not by the kernel directly. In the following sections, we will study how processes go about sending and receiving signals.