Signal management in multi-threaded processes resulted from a compromise among many and sometimes conflicting goals. The goal of compatibility is assured: signals in multi-threaded processes are an extension of signals in traditional single-threaded programs. Programs handling signals and written for single-threaded systems will behave as expected in AIX Version 4.
Signal management in multi-threaded processes is shared by the process and thread levels, and consists of:
The threads library also provides a new subroutine and introduces new programming practices for waiting for asynchronously generated signals.
Signal handlers are maintained at process level. It is strongly recommended to use only the sigaction subroutine to get and set signal handlers. Other subroutines may not be supported in the future.
Because the list of signal handlers is maintained at process level, any thread within the process may change it. If two threads set a signal handler on the same signal, the last thread that called the sigaction subroutine will override the setting of the previous thread call; and in most cases, it will be impossible to predict the order in which threads are scheduled.
Signal masks are maintained at thread level. Each thread can have its own set of signals that will be blocked from delivery. The sigthreadmask subroutine must be used to get and set the calling thread's signal mask. The sigprocmask subroutine must not be used in multi-threaded programs; otherwise, unexpected behavior may result.
The sigthreadmask subroutine is very similar to sigprocmask. The parameters and usage of both subroutines are exactly the same. When porting existing code to support the threads library, you may simply replace sigprocmask with sigthreadmask.
Signals generated by some action attributable to a particular thread, such as a hardware fault, are sent to the thread that caused the signal to be generated. Signals generated in association with a process ID, a process group ID, or an asynchronous event (such as terminal activity) are sent to the process.
The pthread_kill subroutine sends a signal to a thread. Because thread IDs identify threads within a process, this subroutine can only send signals to threads within the same process.
The kill subroutine (and thus the kill command) sends a signal to a process. A thread can send a signal Signal to its process by executing the following call:
kill(getpid(), Signal);
The raise subroutine cannot be used to send a signal to the calling thread's process. The raise subroutine sends a signal to the calling thread, as in the following call:
pthread_kill(pthread_self(), Signal);
This ensures that the signal is sent to the caller of the raise subroutine. Thus, library routines written for single-threaded programs may easily be ported to a multi-threaded system, because the raise subroutine is usually intended to send the signal to the caller.
The alarm subroutine requests that a signal be sent later to the process, and alarm states are maintained at process level. Thus, the last thread that called the alarm subroutine overrides the settings of other threads in the process. In a multi-threaded program, the SIGALRM signal is not necessarily delivered to the thread that called the alarm subroutine. The calling thread may even be terminated; and therefore, it cannot receive the signal.
Signal handlers are called within the thread to which the signal is delivered. Signal handlers may call the pthread_self subroutine to get their thread ID. Some limitations to signal handlers are introduced by the threads library:
Usually, a program that wants to wait for a signal installs a signal handler that calls the longjmp subroutine to continue execution at the point where the corresponding setjmp subroutine was called. This cannot be done in a multi-threaded program, because the signal may be delivered to a thread other than the one that called the setjmp subroutine, thus causing the handler to be executed by the wrong thread.
To allow a thread to wait for asynchronously generated signals, the threads library provides the sigwait subroutine. The sigwait subroutine blocks the calling thread until one of the awaited signals is sent to the process or to the thread. There must not be a signal handler installed on a signal awaited using the sigwait subroutine.
Typically, programs may create a dedicated thread to wait for asynchronously generated signals. Such a thread just loops on a sigwait subroutine call and handles the signals. The following code fragment gives an example of such a signal waiter thread:
sigset_t set; int sig;
sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); sigaddset(&set, SIGTERM); sigthreadmask(SIG_BLOCK, &set, NULL);
while (1) { sigwait(&set, &sig); switch (sig) { case SIGINT: /* handle interrupts */ break; case SIGQUIT: /* handle quit */ break; case SIGTERM: /* handle termination */ break; default: /* unexpected signal */ pthread_exit((void *)-1); } }
If more than one thread called the sigwait subroutine, exactly one call returns when a matching signal is sent. There is no way to predict which thread will be awakened. Note that the sigwait subroutine provides a cancellation point.
Because a dedicated thread is not a real signal handler, it may signal a condition to any other thread. It is possible to implement a sigwait_multiple routine that would awaken all threads waiting for a specific signal. Each caller of the sigwait_multiple routine would register a set of signals. The caller then waits on a condition variable. A single thread calls the sigwait subroutine on the union of all registered signals. When the call to the sigwait subroutine returns, the appropriate state is set and condition variables are broadcasted. New callers to the sigwait_multiple subroutine would cause the pending sigwait subroutine call to be canceled and reissued to update the set of signals being waited for.
A signal is delivered to a thread, unless its action is set to ignore. The following rules govern signal delivery in a multi-threaded process:
Consider for example a multi-threaded user command, such as the grep command. A user may start the command in his favorite shell and then decide to stop it by sending a signal with the kill command. It is obvious that the signal should stop the entire process running the grep command.
If the action associated with a pending signal (on a thread or on a process) is set to ignore, the signal is ignored.
Threads-Processes Interactions Overview.
Process Duplication and Termination.
List of Threads-Processes Interactions Subroutines.