The old signal facility of UNIX is rather limited, but it is available
on every implementation of UNIX or one of its clones. Originally, signals
were used to kill another process. Therefore, for historical
reasons, the
system call by which a process can send a signal to another process is
called kill(). There is a set of signals, each identified by a
number (they
are defined in
signal.h
), and the complete system call for
sending a
signal to a process is:
kill(pid_t pid, int signal);
The integer signal is usually specified symbolically:
SIGINT,
SIGALRM or SIGKILL, etc., as defined in
signal.h
.
pid is the process identification of the process to which the
signal shall
be sent. If this receiving process has not been set up to
intercept signals,
its execution will simply be terminated by any signal sent to it. The
receiving process can however be set up to intercept certain
signals and to
perform certain actions upon reception of such an intercepted signal.
Certain signals cannot be intercepted, they are just killers: SIGINT,
SIGKILL are examples. In order to intercept a signal, the
receiving process
must have set up a signal handler and notified this to the
operating system with
the sigaction() system call. The following is an example of
how this can be done:
A structure sigaction (not to be confounded with the system call of the same name!) is defined as follows:
struct sigaction {
void (*sa_handler)();
sigset_t sa_mask;
int sa_flags;
void(*sa_sigaction)(int,siginfo_t *,void *); };
This structure encapsulates the action to be taken on receipt of a signal.
The following is a program that shall exit gracefully when it receives the signal SIGUSR1. The function terminate_normally() is the signal handler. The administrative things are accomplished by defining the elements of the structure and then calling sigaction() to get the signal handler registered by the operating system.
void
terminate_normally(int signo)
{
/* Exit gracefully */
exit(0);
}
main(int argc, char **argv)
{
struct sigaction sa;
sa.sa_handler = terminate_normally;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGUSR1, &sa, NULL)) {
perror("sigaction");
exit(1);
}
...
}
The operating system itself may generate signals, for instance as the result of machine exceptions: floating point exception, page fault, etc. Signals may also be generated by something which happens asynchronously with the process itself. The signals then aim at interrupting the process: I/O completion, timer expiration, receipt of a message on an empty message queue, or typing CTRL-C or CTRL-Z on the keyboard. Signals can also be sent from one user process to another.
The structure sigaction does not only contain the information
needed
to register the signal handler with the operating system (in the process
descriptor), but it also contains information on what the receiving
process should do when it receives the registered signal. It can do one
of three things with the signal:
- it can block the signal for some time and later unblock it.
- it can ignore the signal, pretending that nothing has happened.
- it can handle the signal, by executing the signal handler.
The POSIX.1 signals, described so far, have some serious limitations:
- there is a lack of signals for use by a user application (there
are only two: SIGUSR1 and SIGUSR2).
- signals are not queued. If a second signal is sent to a process before
the first one could be handled, the first one is simply and irrevocably
lost.
- signals do not carry any information, except for the number of the
signal.
- and, last but not least, signals are sent and received asynchronously.
This means in fact that a process may receive a signal at any time, for
instance also when it is updating some sensitive data-structures. If the
signal handler will also do something with these same data-structures, you
may be in deep trouble. In other words, when you write your program, you
must always keep in mind that you may receive a signal exactly at the
point where your pencil is.
Linux is compliant with this POSIX 1003.1a definition of signals.