Software Tool & Die
Copyright (c) 1994 Jim Frost
All Rights Reserved
Last changed August 17, 1994
This document describes the four common UNIX signalling environments, including their interfaces, and contrasts each. In addition, it describes the concepts and interfaces behind both BSD and POSIX process groups.
There are four common signal-handling environments today. They are BSD, System-V unreliable, System-V reliable, and POSIX. This section discusses each and common variations where appropriate.
Traditional System-V inherited its signal-handling environment from V7 research UNIX. This environment has three major limitations:
These limitations cause "unreliable" signal behavior: Since signals can be delivered recursively and the signal handler must manually reset the signal handler from SIG_DFL, there is a window during which the default signal handler can be called if another signal of the same type arrives. In many cases the default action is to ignore the signal, causing the signal to be lost. In the worst case the default action is to terminate the process.
Additionally any signal can interrupt a system call. The window for interrupting a system call is fairly small for most system calls (and impossible for some) but large for some common "slow" system calls such as read() or write(). Few applications attempt to restart system calls that have been interrupted by a signal. This results in even more unreliability.
The interface to the traditional signal handling environment is:
, int (*function
Two standard signal handlers exist:
Because of the problems inherent in V7 signal handling, the BSD developers modified the signal-handling semantics somewhat:
This worked around the "unreliable" semantics of traditional System-V signal handling and added critical-section protection at the same time. Both are essential for reliable applications.
The BSD interface includes the traditional System-V interface (using the new reliable semantics) but adds the following:
, struct sigvec *new_sigvec
, struct sigvec *old_sigved
int sigstack(char *stack
SV_INTERRUPTproperty of a signal handler. If interrupt is zero, system calls will be restarted after signal delivery. If it is non-zero they will return
sigvec structure defines the signal-handling
The sv_flags field describes the options you could use to alter signal handling. Standard options are:
Not all of these options are supported by "BSD-compatible" systems
SV_RESETHAND was not supported until BSD 4.3.
The same set of standard signal handlers is used as in V7 and traditional System-V.
Because of the problems caused by unreliable signals AT&T added a new interface to give reliable signal-handling semantics to System-V at release 3 (SVR3). Unfortunately this interface differs from -- and is less flexible than -- the BSD interface. It is unclear why AT&T picked a conflicting interface given that the BSD interface easily predated the newer System-V.
The System-V approach gives a signal environment very similar to
that of BSD, except that a call to the
clears the signal mask, causing it to be impossible to establish a
critical section during which a signal handler can be changed.
The interface is:
, int (*function
sigpause() conflicts with the BSD function
of the same name, and that
sigset() has an additional
standard signal handler,
SIG_HOLD, which is used to block
a signal temporarily.
The most important limitation of the this signal interface is the inability to atomically unblock and wait for more than one signal at a time.
To make certain that no one could write an easily portable application, the POSIX committee added yet another signal handling environment which is supposed to be a superset of BSD and both System-V environments.
Depending on the particular vendor, the POSIX approach can usually
be used to emulate all of the environments discussed here, and appears
to be derived from the BSD
sigvec() interface. Few
vendors actually implement all of the options, however, and the POSIX
committee did not standardize the common options. Thus few
implementations of the POSIX signal handling environment are
The biggest advantage in using the POSIX interface is the ability to deal with more signals than can fit in an integer -- more than the 32 used in BSD.
The POSIX interface is:
, struct sigaction *new_handler
, struct sigaction *old_handler
, sigset_t *new_set
, sigset_t *old_set
int sigemptyset(sigset_t *set
int sigfillset(sigset_t *set
int sigaddset(sigset_t *set
int sigdelset(sigset_t *set
int sigismember(sigset_t *set
int sigpending(sigset_t *set
sigaction structure describes the signal handling
The same default signal handlers are used in POSIX as in V7.
Common options for
SIGCHLDfor stopped (versus terminated) processes.
SA_NOCLDSTOP is required by the POSIX
specification. Other options are often missing or have different
One of the areas least-understood by most UNIX programmers is process-group management, a topic that is inseparable from signal-handling.
To understand why process-groups exist, think back to the world before windowing systems.
Your average developer wants to run several programs simultaneously -- usually at least an editor and a compilation, although often a debugger as well. Obviously you cannot have two processes reading from the same tty at the same time -- they'll each get some of the characters you type, a useless situation. Likewise output should be managed so that your editor's output doesn't get the output of a background compile intermixed, destroying the screen.
This has been a problem with many operating systems. One solution, used by Tenex and TOPS-20, was to use process stacks. You could interrupt a process to run another process, and when the new process was finished the old would restart.
While this was useful it didn't allow you to switch back and forth between processes (like a debugger and editor) without exiting one of them. Clearly there must be a better way.
The Berkeley UNIX folks came up with a different idea, called process groups. Whenever the shell starts a new command each process in the command (there can be more than one, eg "ls | more") is placed in its own process group, which is identified by a number. The tty has a concept of "foreground process group", the group of processes which is allowed to do input and output to the tty. The shell sets the foreground process group when starting a new set of processes; by convention the new process group number is the same as the process ID of one of the members of the group. A set of processes has a tty device to which it belongs, called its "controlling tty". This tty device is what is returned when /dev/tty is opened.
Because you want to be able to interrupt the foreground processes, the tty watches for particular keypresses (^Z is the most common one) and sends an interrupt signal to the foreground process group when it sees one. All processes in the process group see the signal, and all stop -- returning control to the shell.
At this point the shell can place any of the active process groups back in the foreground and restart the processes, or start a new process group.
To handle the case where a background process tries to read or
write from the tty, the tty driver will send a
SIGTTOU signal to any background process which attempts
to perform such an operation. Under normal circumstances, therefore,
only the foreground process(es) can use the tty.
The set of commands to handle process groups is small and straightforward. Under BSD, the commands are:
, TIOCSETPGRP, intforeground_group
, TIOCGETPGRP, int *foreground_group
, TIOCNOTTY, 0);
The BSD process-group API is rarely used today, although most of the concepts survive. The POSIX specification has provided new interfaces for handling process groups, and even overloaded some existing ones. It also limits several of the calls in ways which BSD did not.
The POSIX process-group API is:
, int *foreground_group
int kill(int -process_group
setpgrp() function is called
setpgid() under POSIX and is essentially identical. You
must be careful under POSIX not to use the
setpgrp() function -- usually it exists, but performs the operation of
getpgrp() function was renamed
getpgid() can only inspect
the current process' process group.
killpgrp() function doesn't exist at all.
Instead, a negative value passed to the
is taken to mean the process group. Thus you'd perform
) by calling
ioctl() commands for querying and changing the
foreground process group are replaced with first-class functions:
, int *process_group
While the original BSD
ioctl() functions would allow
any tty to take on any process group (or even nonexistant process
groups) as its foreground tty, POSIX allows only process groups which
have the tty as their controlling tty. This limitation disallows some
ambiguous (and potentially security-undermining) cases present in BSD.
The TIOCNOTTY ioctl used in BSD is replaced with the
setsid() function, which is essentially identical to:
It releases the current tty and puts the calling process into its own process group. Notice that nothing is done if the calling process is already in its own process group -- this is another new limitation, and eliminates some ambiguous cases that existed in BSD (along with some of BSD's flexibility).