http://www.cis.temple.edu/~giorgio/cis307/readings/signals.html
Unix Signals
Signals represent a very limited form of interprocess communication. They are easy to use (hard to use well) but they communicate very little information. In addition the sender (if it is a process) and the receiver must belong to the same user id, or the sender must be the superuser. Signals are sent explicitly to a process from another process using the kill function. Signals can indicate some significant terminal action, such as Hang Up. Alternatively, signals are sent to a process from the hardware (to indicate things like illegal operator, or illegal address) through mediation of the OS. Beware that signals could also be caused by an internet connection, for example a TCP/IP OOB (out of band) message could cause the SIGURG signal. There are only a few possible signals (usually 32 - wait: I just checked on my GNU/Linux 2.6.17-1.2142_FC4 and I have now 64 possible signals!)). A process can specify with a mask (1 bit per signal) what it wants to be done with a signal directed to it, whether to block it or to deliver it. Blocked signals remain pending, i.e. we may ask to have them delivered later. A process specifies with the signal function (obsolete) or with the sigaction function what it wants it to be done when signals are delivered to it. When a signal is delivered to a process (i.e. it is not blocked), an action takes place. For each signal there is a default action specific to that signal. Or for a signal an action (a handler) can be specified by the program.
There are three actions that can take place when a signal is delivered to a process:
- it can be ignored; or
- the process can be terminated (with or without core dumping); or
- a handler function can be called. This function receives as its only argument the number identifying the signal it is handling.
The association of a signal to an action remains in place until it is explicitly modified with a signal or sigaction call. [This is the behavior at least in modern Unix systems. In older Unix after each signal handling occurrence the handler reverted to the default handler. This created a race condition if we were intending to re-establish a non default handler. For this reason old Unix was said to be with unrelieable signals.]
In writing a handler function one has to be conscious that it is executed as an asynchronous action during the execution of a process (well, there are also synchronous signals like SIGBUS due to the use of an illegal address). The handler code may execute a routine that was interrupted by the signal, or access variables that were being used in the process. Thus one has to be careful about the code that is executed in the handler and in the variables it accesses. [You may want to examine in C or C++ the use of the attribute volatile.] During execution of the handler associated to a signal, that specific signal is automatically blocked thus preventing a race condition. But beware that if the same handler is specified for two different signals A and B, then during the execution of the handler for A, a B signal will not be blocked and the handler will be reentered. After the handler function is completed, execution resumes at the statement being executed when the signal was received. If the signal occurred while the process was executing a system call, things become more comples. The system call may be terminated without completing and return the value EINTR. In some systems it is possible to specify that interrupted system calls be automatically restarted by using the flag SA_RESTART. In other system SA_RESTART is the default.
The following diagram describes how a signal is raised, possibly blocked before delivery, and then handled.
We may worry that signals may be lost while pending, but that is not the case. If multiple copies of a signal are delivered to a process while that signal is blocked, normally only a single copy of that signal will be delivered to the process when the signal becomes unblocked.
Here are some of the possible signals, with the number associated to them, and their default handling.
SIGNAL ID DEFAULT DESCRIPTION ====================================================================== SIGHUP 1 Termin. Hang up on controlling terminal SIGINT 2 Termin. Interrupt. Generated when we enter CNRTL-C and it is delivered to all processes/threads associated to the current terminal. If generated with kill, it is delivered to only one process/thread. SIGQUIT 3 Core Generated when at terminal we enter CNRTL-\ SIGILL 4 Core Generated when we executed an illegal instruction SIGTRAP 5 Core Trace trap (not reset when caught) SIGABRT 6 Core Generated by the abort function SIGFPE 8 Core Floating Point error SIGKILL 9 Termin. Termination (can't catch, block, ignore) SIGBUS 10 Core Generated in case of hardware fault SIGSEGV 11 Core Generated in case of illegal address SIGSYS 12 Core Generated when we use a bad argument in a system service call SIGPIPE 13 Termin. Generated when writing to a pipe or a socket while no process is reading at other end SIGALRM 14 Termin. Generated by clock when alarm expires SIGTERM 15 Termin. Software termination signal SIGURG 16 Ignore Urgent condition on IO channel SIGCHLD 20 Ignore A child process has terminated or stopped SIGTTIN 21 Stop Generated when a backgorund process reads from terminal SIGTTOUT 22 Stop Generated when a background process writes to terminal SIGXCPU 24 Discard CPU time has expired SIGUSR1 30 Termin. User defiled signal 1 SIGUSR2 31 Termin. User defined signal 2
One can see the effect of these signal by executing in the shell the kill command. For example
% kill -TERM pidid
You may see what are the defined signals with the shell command
% kill -l
It is easy to see that some signals occur synchronously (i.e. they are directly related to the instruction being executed) with the executing program (for example SIGSEGV), others asynchronously (for example SIGINT), and others are explicitly directed from one process to another (for example SIGKILL).
Here are some basic functions for signals:
#include <signal.h> void (*signal(int sign, void(*function)(int)))(int); The signal function takes two parameters, an integer and the address of a function of one integer argument which gives no return. Signal returns the address of a function of one integer argument that returns nothing. sign identifies a signal the second argument is either SIG_IGN (ignore the signal) or SIG_DFL (do the default action for this signal), or the address of the function that will handle the signal. It returns the previous handler to the sign signal. The signal function is still available in modern Unix systems, but only for compatibility reasons. It is better to use sigaction. #include <signal.h> void sigaction(int signo, const struct sigaction *action, struct sigaction *old_action); where struct sigaction { void (*sa-handler) (int); /*address of signal handler*/ sigset_t sa_mask; /*signals to block in addition to the one being handled*/ int sa_flags;}; /*It specifies special handling for a signal. For simplicity we will assume it is 0.*/ /*The possible values of sa_handler are: SIG_IGN: ignore the signal SIG_DFL: do the default action for this signal or the address of the signal handler There is also a more complex form of this structure with information for using alternate stacks to handle interrupts */
Objects of type sigset_t can be manipulated with the functions:
#include <signal.h> int sigemptyset(sigset_t * sigmask); int sigaddset(sigset_t * sigmask, const int signal_num); int sigdelset(sigset_t * sigmask, const int signal_num); int sigfillset(sigset_t * sigmask); int sigismember(const sigset_t * sigmask, const int signal_num); that have the meaning implied by their names.
The programmer can control (set or read) which signals are blocked [a blocked signal remains pending until the program unblocks that signal and the signal is delivered] with the sigprocmask function.
#include <signal.h> int sigprocmask(int cmd, const sigset_t* new_mask, sigset_t* old_mask); where the parameter cmd can have the values SIG_SETMASK: sets the system mask to new_mask SIG_BLOCK: Adds the signals in new_mask to the system mask SIG_UNBLOCK: Removes the signals in new_mask from system mask If old_mask is not null, it is set to the previous value of the system mask #include <sys/types.h> #include <signal.h> int kill(pid_t process-id, int sign); Sends the signal sign to the process process-id. [kill may also be used to send signals to groups of processes.]
Other useful functions are:
#include <unistd.h> unsigned int alarm(unsigned int n); It requests the delivery in n seconds of a SIGALRM signal. If n is 0 it cancels a requested alarm. It returns the number of seconds left for the previous call to alarm (0 if none is pending). #include <unistd.h> int pause(void); It requests to be put to sleep until the process receives a signal. It always returns -1. #include <signal.h> int sigsuspend(const sigset_t *sigmask); It saves the current (blocking) signal mask and sets it to sigmask. Then it waits for a non-blocked signal to arrive. At which time it restores the old signal mask, returns -1, and sets errno to EINTR (since the system service was interrupted by a signal). It is used in place of pause when afraid of race conditions in the situation where we block some signals, then we unblock and would like to wait for one of them to occur.
The use of the function signal is being replaced in recent Unix versions by the use of the function sigaction which is more complex and uses signal sets. The functions signal and pause are still being supported for compatibility reasons.
Here is an example of a program that uses sigaction and sigsuspend instead of signal and pause.
Since the use of signal is easier than the use of sigaction, Stevens suggests the use of the following definitions (slightly modified):
typedef void Sigfunc(int); /* Sigfunc is type of function with one int arg, and void return */ Sigfunc * signal(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_RESTART if (signo != SIGALRM) act.sa_flags |= SA_RESTART; #endif if (sigaction(signo, &act, &oact) < 0) return (SIG_ERR); return (oact.sa_handler); }
And here is a use of this function. This program binds the signal SIGCHLD to a handler sig_cld, then it loops ten times. In each iteration it creates a child and pauses. The handler is called when the child terminates. It collects and prints out information about the defunct child. Notice that in this handler we do not need, as it was necessary in old Unix implementations, to reestablish the handler.
For another more complex way to handle SIGCHLD signals see this implementation in the GNU C Library.
To use signals well is very tricky. Signals can interfere with system services. For example, what happens if we receive an exception while executing a system call? The answer depends on the specific call. Usually time consuming operations such as IO operations are interrupted by a signal. For example if we are reading from a file we may be interrupted. We find out about it by checking errno for the value EINTR. For example:
bytesread = read(input_fd, buf, BLKSIZE); if (bytesread < 0) if (errno == EINTR) printf("The read was interrupted. You can try to read again\n"); else printf("Error in read. Don't know what to do about it\n");
Setjmp and longjmp
The C language has four functions that often are used in conjunction with signals and their handlers, or more in general to deal with error conditions. These functions are setjmp, siglongjmp, longjmp, sigsetjmp. These topics are discussed in the GNU C Library. Here is a simple example of using non local jumps.