home *** CD-ROM | disk | FTP | other *** search
-
- UNIX SIGNALS AND PROCESS GROUPS
-
-
-
- Jim Frost
- Software Tool & Die
- Copyright (c) 1994 Jim Frost
- All Rights Reserved
- Last changed August 17, 1994
-
- _________________________________________________________________
-
- Contents
-
- * Abstract
- * UNIX Signal Handling Environments
- + The Traditional System-V Signal Environment
- + The BSD 4.X Signal Environment
- + The System-V.3 Reliable Signal Environment
- + The POSIX Signal Environment
- * Process Groups and Tty Management
- + The Berkeley Approach
- + The POSIX Approach
- + Differences between POSIX and BSD Process Group Management
-
-
- _________________________________________________________________
-
- Abstract
-
-
-
- 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.
-
- UNIX Signal Handling Environments
-
-
-
- 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.
-
- THE TRADITIONAL SYSTEM-V SIGNAL ENVIRONMENT
-
-
-
- Traditional System-V inherited its signal-handling environment from V7
- research UNIX. This environment has three major limitations:
-
- * Recursive signal handling is always allowed.
- * Signal handlers are reset to SIG_DFL prior to being called.
- * System calls interrupt when a signal is delivered.
-
-
-
- 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 (*signal)(int signal_number, int (*function)(int));
-
-
- Change the signal handler for the indicated signal.
- Signal_number indicates which signal, function is the new
- handler.
-
- int pause(void);
-
-
- Wait for a signal to arrive.
-
-
-
- Two standard signal handlers exist:
-
-
-
- SIG_DFL
- Take the default action for this signal.
-
- SIG_IGN
- Ignore this signal.
-
- THE BSD 4.X SIGNAL ENVIRONMENT
-
-
-
- Because of the problems inherent in V7 signal handling, the BSD
- developers modified the signal-handling semantics somewhat:
-
- * Signals are blocked for the duration of a signal handler (i.e.
- recursive signals are not normally allowed).
- * A "signal mask" can be set to block most signals during critical
- regions.
- * Signal handlers normally remain installed during and after signal
- delivery.
- * A separate signal handler stack can be used if desired.
- * Most system calls are restarted following delivery of a signal.
-
-
-
- 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:
-
-
-
- int sigvec(int signal_number, struct sigvec *new_sigvec, struct sigvec
- *old_sigved);
-
-
- Change or query signal handlers. Signal_number indicates which
- signal. New_sigvec defines the handler environment or may be
- null if you don't want to change the handler. If non-null,
- old_sigvec is filled with the current handler information.
-
- int sigstack(char *stack);
-
-
- Change the stack which will be used during signal delivery.
-
- int siginterrupt(int signal_number, int interrupt);
-
-
- Alter the SV_INTERRUPT property of a signal handler. If
- interrupt is zero, system calls will be restarted after
- signal delivery. If it is non-zero they will return EINTR.
-
- int sigsetmask(int new_mask);
-
-
- Change the set of masked/held signals. New_mask is a bit
- pattern describing the new set of signals. The old signal mask
- is returned.
-
- int sigblock(int signal_mask);
-
-
- Add a new set of signals to the current signal mask.
-
- int sigpause(int signal_mask);
-
-
- Wait for a signal using the given signal mask.
-
-
-
- The sigvec structure defines the signal-handling semantics:
-
-
- struct sigvec {
- int (*sv_handler)(void); /* signal handler */
- int sv_mask; /* signals to mask during signal delivery */
- int sv_flags; /* signal delivery options */
- };
-
-
-
- The sv_flags field describes the options you could use to alter signal
- handling. Standard options are:
-
-
-
- SV_ONSTACK
- Use a special stack rather than the normal hardware stack. The
- stack to use must be specified using sigstack().
-
- SV_RESETHAND
- Use V7-style "reset to default handler" signal handling
- semantics.
-
- SV_INTERRUPT
- Allow system calls to be interrupted by signal delivery.
-
-
-
- Not all of these options are supported by "BSD-compatible" systems and
- 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.
-
- THE SYSTEM-V.3 RELIABLE SIGNAL ENVIRONMENT
-
-
-
- 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 sigset() function 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 (*sigset)(int signal_number, int (*function)(int));
-
-
- Change the signal handler for the indicated signal.
-
- int sighold(int signal_number);
-
-
- Hold/mask a signal.
-
- int sigrelse(int signal_number);
-
-
- Release/unmask a held signal.
-
- int sigignore(int signal_number);
-
-
- Ignore a signal.
-
- int sigpause(int signal_number);
-
-
- Pause for a signal, unmasking the given signal.
-
-
-
- Note that 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.
-
- THE POSIX SIGNAL ENVIRONMENT
-
-
-
- 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 identical.
-
- 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:
-
-
-
- int sigaction(int signal_number, struct sigaction *new_handler, struct
- sigaction *old_handler);
-
-
- Set or query the signal handling environment of a given signal.
-
-
- int sigprocmask(int how, sigset_t *new_set, sigset_t *old_set);
-
-
- Set or query the signal mask. If new_set is null, no change is
- made. If old_set is null, nothing is returned.
-
- int sigemptyset(sigset_t *set);
-
-
- Clear a signal mask.
-
- int sigfillset(sigset_t *set);
-
-
- Fill (add all possible signals to) a signal mask.
-
- int sigaddset(sigset_t *set, int signal_number);
-
-
- Add a signal to a signal mask.
-
- int sigdelset(sigset_t *set, int signal_number);
-
-
- Remove a signal from a signal mask.
-
- int sigismember(sigset_t *set, int signal_number);
-
-
- See if a signal is a member of a signal mask.
-
- int sigpending(sigset_t *set);
-
-
- Return the set of signals that have been delivered but which
- were blocked.
-
-
-
- The sigaction structure describes the signal handling environment:
-
-
- struct sigaction {
- void (*sa_handler)(int);
- sigset_t sa_mask; /* new signal mask */
- int sa_flags; /* options */
- };
-
-
-
- The same default signal handlers are used in POSIX as in V7.
-
- Common options for sigaction() include:
-
-
-
- SA_OLDSTYLE
-
- SA_RESETHAND
- V7 unreliable signal semantics.
-
- SA_INTERRUPT
- Allow system calls to be interrupted by signal delivery.
-
- SA_RESTART
- Allow system calls to be restarted after signal delivery.
-
- SA_ONSTACK
- Use a special stack during signal handling.
-
- SA_NOCLDSTOP
- Disable SIGCLD/SIGCHLD for stopped (versus terminated)
- processes.
-
-
-
- Only SA_NOCLDSTOP is required by the POSIX specification. Other
- options are often missing or have different names.
-
- Process Groups and Tty Management
-
-
-
- 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 APPROACH
-
-
-
- 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 SIGTTIN or 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:
-
-
-
- int setpgrp(int process_id, int group_number);
-
-
- Move a process into a process group. If you are creating a new
- process group the group_number should be the same as
- process_id. If process_id is zero, the current process is
- moved.
-
- int getpgrp(int process_id);
-
-
- Find the process group of the indicated process. If process_id
- is zero, the current process is inspected.
-
- int killpgrp(int signal_number, int group_number);
-
-
- Send a signal to all members of the indicated process group.
-
- int ioctl(int tty, TIOCSETPGRP, int foreground_group);
-
-
- Change the foreground process group of a tty.
-
- int ioctl(int tty, TIOCGETPGRP, int *foreground_group);
-
-
- Find the foreground process group of a tty.
-
- int ioctl(int tty, TIOCNOTTY, 0);
-
-
- Disassociate this process from its controlling tty. The next
- tty device that is opened will become the new controlling tty.
-
- THE POSIX APPROACH
-
-
-
- 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 setpgid(int process_id, int process_group);
-
-
- Move a process into a new process group. Process_id is the
- process to move, process_group is the new process group.
-
- int getpgid(int process_id);
-
-
- Find the process group of a process. Process_id is the process
- to inspect.
-
- int getpgrp(void);
-
-
- Find the process group of the current process. This is
- identical to getpgrp(getpid()).
-
- int tcsetpgrp(int tty, int foreground_group);
-
-
- Change the foreground process group of a tty. Tty is the file
- descriptor of the tty to change, foreground_group is the new
- foreground process group.
-
- int tcgetpgrp(int tty, int *foreground_group);
-
-
- Find the foreground process group of a tty. Tty is the file
- descriptor of the tty to inspect, foreground_group is returned
- filled with the foreground process group of the tty.
-
- int kill(int -process_group, int signal_number);
-
-
- Send a signal to a process group. Note that process_group must
- be passed as a negative value, otherwise the signal goes to the
- indicated process.
-
- DIFFERENCES BETWEEN POSIX AND BSD PROCESS GROUP MANAGEMENT
-
-
-
- The 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
- setsid().
-
- The getpgrp() function was renamed getpgid(), and getpgid() can only
- inspect the current process' process group.
-
- The killpgrp() function doesn't exist at all. Instead, a negative
- value passed to the kill() function is taken to mean the process
- group. Thus you'd perform killpgrp(process_group) by calling
- kill(-process_group).
-
- The ioctl() commands for querying and changing the foreground process
- group are replaced with first-class functions:
- * int tcsetpgrp(int tty, int process_group);
- * int tcgetpgrp(int tty, 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:
-
-
- if (getpgrp() != getpid()) {
- ioctl(tty, TIOCNOTTY, 0);
- setpgrp(getpid(), getpid());
- }
-
-
-
- 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).
-