The POSIX committee's version of signal handling is a little more complex than the earlier signal() call (isn't that what committees are for?) but it does specify signal semantics which overcome the earlier problems.
Under POSIX, each process has a signal mask which is a set of signals that are currently blocked from delivery. If a blocked signal is sent to a process anyway, it will be added to a set of pending signals for the process which will be delivered when the block is removed.
When a signal is sent it is automatically added to the signal mask of the receiving process so that further instances of that signal are blocked while the current instance is being serviced. When the signal handler returns normally, the signal mask is then restored to its previous value.
The main system call for setting up signal handlers is called sigaction() and its prototype is:
#include <signal.h> #include <unistd.h> int sigaction(int signum, struct sigaction *new, struct sigaction *old);
Instead of just specifying a handler function for a signal, the sigaction() call takes a pointer to a struct sigaction (new) as a parameter. The layout of a struct sigaction is:
struct sigaction { void (*sa_handler) (int); sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer) (void); }
As you can see, the pointer to the handler function has been moved into this structure in the sa_handler field. The sa_mask field in the structure is an extra signal mask which is ORed into the signal mask of the process when this signal occurs. This extra mask stays in force while the signal handler executes. The sa_flags field is an ORed combination of several bit flags, the two main ones being:
SA_ONESHOT set signal action to default when signal occurs; SA_NOMASK ignore struct sigaction sa~mask field.
Both flags are set by default if you use the signal() call rather than sigaction(). The return value from sigaction() does not include a pointer to the old signal handler as it does with the signal() call. Instead, an extra parameter (old) is passed into sigaction(). This is just a pointer to another struct sigaction which will be filled by the sigaction() call with the details from the old sigaction structure. This allows the old values to be restored later if required.
Sometimes you will want to run a process which generates lots of child processes but which does not wish to wait() for them. By default, child processes in this position will become zombies until some process (probably init) waits for them in the future. If the parent is a very long lived process then the zombies will be hanging around in the system for a long time. The creation of zombies under these circumstances just wastes system resources. There are several ways round the problem under Linux, the simplest being to set the action of the SIGCHLD signal to SIG_IGN, as follows:
signal (SIGCHLD, SIG_IGN);
As the default action of SIGCHLD is to be ignored anyway, setting this signal to SIG_IGN serves no other useful purpose. This is therefore used as a special feature in Linux to tell the kernel not to generate zombies from the children of the calling process.
Setting and modifying signal masks is performed by a special set of functions, whose prototypes are:
#include <signal.h> int sigemptyset(sigset_t *mask); int sigfillset(sigset_t *mask); int sigaddset(sigset_t *mask, int signum); int sigdelset(sigset_t *mask, int signum); int sigismember(sigset_t *mask, int signum);
The sigemptyset() function takes a pointer (mask) to a signal mask (of type sigset_t) and clears it to ensure that no signals are flagged as blocked. The function sigfillset() fills a signal mask to ensure that all signals are flagged as blocked. The sigaddset() and sigdelset() functions allow signal blocks for individual signals (signum) to be added and deleted respectively. The final function (sigismember()) allows you to determine if a particular signum is flagged as blocked in *mask.
A process can also change and examine the value of its own signal mask using the sigprocmask() system call:
#include <signals.h> int sigprocmask(int fcn, sigset_t *mask sigset_t *old);
The mask parameter points to a signal mask value which will be applied to the signal mask of the process according to the specification of fcn. The various values for fcn and their meanings are:
The SIGKILL and SIGSTDP signals cannot be blocked and any attempt to do so will just be silently ignored.
The old parameter is a pointer to a signal mask which will contain the previous signal mask value after the call, so that this can be restored later if required.
A process can see if it has any blocked signals pending by calling the sigpending() system call:
#include <signals.h> int sigpending(sigset_t *mask);
The signal mask pointed to by mask is set to indicate any pending signals.