Using Signals in Threads

In a POSIX multithreaded setting, signals are delivered to the process. If a signal is synchronous (the result of the action of a particular thread), the operating system passes the signal on to the thread that generated the exception. Thus, synchronous signals such as SIGFPE (divide by zero), SIGSEGV (addressing violation), and SIGPIPE (broken pipe) would be passed on to the offending thread (which only makes sense). If a synchronous pthread_kill call (see Table 11.9) is used to send a signal, the specified thread also receives the signal. According to POSIX standards, if a signal is asynchronous (not related to a particular thread's activity), such as SIGHUP (hang-up) or SIGINT (interrupt), generated by, say, the kill system call, the decision as to which thread should handle the signal is based upon the signal mask configuration of the threads within the process. If more than one thread has not blocked the received signal, there is no guarantee as to which thread will actually receive the signal. To maintain mutex lock integrity, signals handled by threads that cause the thread to terminate, stopcontinue will cause the process associated with the thread to also terminate, stopcontinue. The details of mutex locking are covered in Section 11.8, "Thread Synchronization." The handling of asynchronous signals by threads in Linux (using the LinuxThreads library implementation) departs somewhat from the POSIX standard. LinuxThreads are LWPs that have their own process IDs. Thus, a signal is always directed to a specific thread.

Table 11.9. The pthread_kill Library Function.

Include File(s)

 

Manual Section

3

Summary

int pthread_kill(pthread_t thread, int signo);

Return

Success

Failure

Sets errno

Nonzero

 

The pthread_kill library function accepts a thread ID (of a sibling) as its first argument and an integer signal value as its second argument. Similar to the kill system call, the existence of a given thread can be checked by setting the signal argument for pthread_kill to 0 and examining the return value of the call. If pthread_kill is successful, the signal is sent, and the function returns a 0. If the call fails, a nonzero value is returned, and the signal is not sent. The return of ESRCH (3) means the specified thread does not exist, while the return of EINVAL (22) means an invalid signal was specified. The pthread_kill call cannot be used to send a signal to a thread in another process.

All the threads within the process share a common table of information specifying what action should be taken upon receipt of a specific signal. Each thread can alter its action for a particular signal (with certain constraints) by using a signal mask. When a thread is created, it inherits its signal mask and priority from its creating thread. The new thread starts with a clean slate and does not inherit any pending (not acted upon) signals from its creator. The signal mask can be manipulated with the pthread_sigmask library function (Table 11.10).

As we are working with signals, an additional header file is required when using this call. The first argument for pthread_sigmask should be one of the defined constants below. These constants indicate how the signal mask should be changed.

Table 11.10. The pthread_sigmask Library Function.

Include File(s)

 

Manual Section

3

Summary

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask);

Return

Success

Failure

Sets errno

Nonzero

 

Constant

Value

Meaning

SIG_BLOCK

1

Block (ignore) the indicated signal(s).

SIG_UNBLOCK

2

Remove the indicated signal(s).

SIG_MASK

3

Replace the current mask with the referenced mask.

The second argument, newmask , is a reference to a sigset_t type object. If we track down the sigset_t data type, which is usually found in the include file , we should find that it is a reference to an array of unsigned long integers. The array is used as a bit mask for signals. If we are modifying the signal mask, the newmask argument should reference the new signal mask. While we can hand-craft the new signal mask, most prefer to use the appropriate POSIX signal mask manipulation functions shown in Table 11.11.

Table 11.11. Signal Mask Manipulation Functions.

Signal Mask Function

Functionality

int sigemptyset(sigset_t *set)

Initialize the signal set to exclude all defined signals.

int sigfillset(sigset_t *set)

Initialize the signal set to include all defined signals.

int sigaddset(sigset_t *set, int signo)

Add the indicated signal to the signal set.

int sigdelset(sigset_t *set, int signo)

Remove the indicated signal from the signal set.

int sigismember(sigset_t *set, int signo)

Returns a nonzero value if the indicated signal is a member of the referenced signal set.

When using the signal mask manipulation functions, be sure to initialize a signal mask before attempting to manipulate it.

The third argument of pthread_sigmask references the current signal mask (a returned value). Thus, if the second argument is set to NULL, making the how argument a don't-care, the function returns the current signal mask via the third argument, *oldmask .

If the pthread_sigmask call is successful, it returns a 0. It returns EFAULT (14) when passed an invalid address for newmask or oldmask . Otherwise, it returns the value EINVAL (22) if the how argument was not SIG_BLOCK, SIG_UNBLOCK, or SIG_MASK.

Program 11.3 demonstrates the use of the pthread_sigmask call and several of the signal mask manipulation calls.

Program 11.3 Using pthread_sigmask .

File : p11.3.cxx /* pthread_sigmask example */ #define _GNU_SOURCE + #define _REENTRANT #include #include #include #include 10 #include #include #include using namespace std; const int MAX=3; + void trapper( int ); int global_i = 0; int main(int argc, char *argv[]) { struct sigaction new_action; 20 new_action.sa_handler = &trapper; new_action.sa_flags = 0; sigset_t my_sigs; // Signal mask int sig_in; setvbuf(stdout, (char *)NULL, _IONBF, 0); + if ( argc > 1 && argc < MAX+2 ) { sigemptyset(&my_sigs); // Clear it out, set to all 0's while( argc-- > 1 ) // Add signal #'s passed in sigaddset(&my_sigs, atoi(argv[argc])); } else { 30 cerr << *argv << " SIG1 ... SIG" << MAX << endl; return 1; } for (int i=1; i < NSIG; ++i) // Attempt to trap all signals sigaction(i, &new_action, NULL); + // BLOCK signals in mask pthread_sigmask(SIG_BLOCK, &my_sigs, NULL); cout << "Signal bits turned on" << endl; for (int i=1; i < NSIG; ++i) putchar( sigismember(&my_sigs, i) == 0 ? '0' : '1'); 40 cout << " Waiting for signals "; // Wait for a few signals while (global_i < MAX){ if ( (sigwait(&my_sigs, &sig_in)) != -1 ) cout << "Signal " << sig_in << " in mask - no signal catcher" << endl; + ++global_i; } return 0; } void 50 trapper( int s ){ cout << "Signal " << s << " not in mask - in signal catcher" << endl; ++global_i; }

Program 11.3 uses its command-line values (assumed to be numeric values representing valid signal numbers ) to build a signal mask. The sigemptyset call clears the array representing the signal mask. Each value passed on the command line is added to the signal mask by a call to sigaddset (line 28). After the signal mask is created, the program uses the sigset call to replace the default action for the receipt of each signal with a call to the user -defined trapper function. The sole purpose of the trapper function is to print a message displaying the numeric value of an incoming signal. The call to pthread_sigmask blocks the receipt of the signals in the constructed signal mask. The specified signals will not be handled by the thread (keep in mind that even though we have not made a call to pthread_create , we are still dealing with a single thread within main ). The content of the signal mask is displayed using the sigismember function (line 39) to verify the presence or absence of specific signals in the signal mask.

The program then waits for the receipt of signals. In a loop the sigwait call (Table 11.12) is used to wait for signals.

Table 11.12. The sigwait Library Function.

Include File(s)

 

Manual Section

3

Summary

int sigwait(const sigset_t *set, int *sig);

Return

Success

Failure

Sets errno

Nonzero

 

The POSIX version of sigwait takes two arguments: a reference to the signal set and a reference to a location to store the returned signal value. If successful, sigwait returns a 0 and a reference to the signal as its second argument (remember, this call waits for any signal, not just those in the signal mask). If the call is not successful, it returns a nonzero value, sets its second argument to -1, and returns the value EINVAL (22), indicating an unsupported signal number was found in the signal set, or EFAULT (14) if passed an invalid signal set address.

It is unwise to use sigwait to manage synchronous signals, such as floating-point exceptions that are sent to the process itself. Additionally, the LinuxThreads library implementation of sigwait installs a dummy signal-catching routine for each signal specified in the signal mask. A sample run of Program 11.3 is shown in Figure 11.6.

Figure 11.6 Output from Program 11.3.

linux$ p11.3 2 3 Signal bits turned on 011000000000000000000000000000000000000000000000000000000000000 Waiting for signals Signal 20 not in mask - in signal catcher <-- 1 Signal 3 in mask - no signal catcher <-- 2 Signal 2 in mask - no signal catcher <-- 3

(1) User enters ^Z from keyboard.

(2) User enters ^ from keyboard.

(3) User enters ^C from keyboard.

In the output shown, produced from a run of Program 11.3, the user passed the program the values 2 and 3 on the command line. The program uses these values to create a signal mask. When signal 2 or 3 is received, the initial thread, generated when main executes, does not handle the signals (that is, the trapper function is not called). When a non-blocked signal is received (for example the ^Z), the default action occurs: A call to the user-defined function trapper is made.

EXERCISE

Program 11.3 can be run in the background so signals that cannot be generated at the keyboard can be sent to the process. For example,

linux$ p11.3 32 33 19 & <-- 1 [1] 5414 linux$ Signal bits turned on 000000000000000000100000000000011000000000000000000000000000000 Waiting for signals linux$ kill -32 5414 <-- 2

(1) Program is placed in the background. The system returns the process ID.

(2) Send signal 32 to process.

When Program 11.3 is passed the set of signal values 32 33 19, what output is produced when, in turn , each of these signals is sent to the process? Why do you get this output?

In a multithreaded setting most authors advocate using a separate thread to handle signal processing. An example of how this can be done is shown in Program 11.4.

Program 11.4 Using a separate thread to handle signal processing.

File : p11.4.cxx /* Handling signals in a separate thread */ #define _GNU_SOURCE + #define _REENTRANT #include #include #include #include 10 #include using namespace std; void *sibling(void *); void *thread_sig_handler(void *); sigset_t global_sig_set; + int global_parent_id; int main( ){ pthread_t t1,t2,t3; sigfillset( &global_sig_set ); // set of all signals 20 // BLOCK all in set pthread_sigmask(SIG_BLOCK, &global_sig_set, NULL); // Create 3 threads pthread_create(&t1, NULL, thread_sig_handler, NULL); pthread_create(&t2, NULL, sibling, NULL); + pthread_create(&t3, NULL, sibling, NULL); global_parent_id = getpid( ); while (1){ cout << "main thread PID: " << getpid() << " TID: " << pthread_self() << endl; 30 sleep(3); } return 0; } void * + sibling(void *arg){ while(1){ cout << "sibling thread PID: " << getpid() << " TID: " << pthread_self() << endl; sleep(3); 40 } return NULL; } void * thread_sig_handler(void *arg) { + int sig; cout << "signal thread PID: " << getpid() << " TID: " << pthread_self() << endl; while(1){ sigwait( &global_sig_set, &sig ); 50 if ( sig == SIGINT ){ cout << "I am dead" << endl; kill( global_parent_id, SIGKILL ); } cout << endl << "signal " << sig << " caught by signal thread " + << pthread_self() << endl; } return NULL; }

In line 21 the program generates and fills a signal mask (indicating all signals). A call to pthread_sigmask blocks the signals (except, of course, those signals that even when requested cannot be blocked). Any threads that are subsequently generated will inherit this information. Next, a thread to handle signal processing is created. This thread runs the code in the thread_sig_handler function. This function identifies itself and enters an endless loop. In the loop the call sigwait causes this thread to block, waiting for the receipt of one of the signals in the signal mask. When a signal is received, it is examined. If the signal is SIGINT, the thread sends the parent process (running the main thread) a SIGKILL signal, which terminates all processing. Notice that due to their declaration placement in the program code, the signal mask and PID of the initial thread (process) are global. Once the signal-processing thread is established, two additional threads are generated. Both of these threads run the code found in the sibling function. This function contains another endless loop. Every couple of seconds the thread running this code identifies itself and displays its process and thread IDs. When all threads have been generated, main also enters a loop where every few seconds it identifies itself (process and thread IDs). A run of this program (shown in Figure 11.7) is very informative.

Figure 11.7 Output from Program 11.4.

linux$ p11.4 signal thread PID: 15760 TID: 1026 sibling thread PID: 15761 TID: 2051 <-- 1 main thread PID: 15758 TID: 1024 sibling thread PID: 15762 TID: 3076 signal 3 caught by signal thread 1026 <-- 2 signal 20 caught by signal thread 1026 sibling thread PID: main thread PID: 15758 TID: 1024 <-- 3 sibling thread PID: 15761 TID: 2051 15762 TID: 3076 I am dead signal 2 caught by signal thread 1026 <-- 4 Killed

(1) Four threads are generated : main, a signal thread and two sibling threads. Each has its own thread ID and is run as a separate process.

(2) User enters ^ and ^Z from the keyboard. These signals are handled by the signal-processing thread.

(3) While output is not buffered, it is still interleaved as threads compete .

(4) Entering a ^C (from the keyboard) terminates processing. The signal-processing thread is able to slip in one last message.

EXERCISE

First, modify Program 11.4 to remove the global reference to the signal mask and parent process ID (check Program 11.2 for one way this can be done). Second, the threads running the sibling function should be passed a signal value that they (the threads) will be responsible for catching and processing. The first sibling should be passed SIGUSR1 and the second sibling passed SIGUSR2. The sibling threads should not make use of sigwait (think sigaction ). Run the modified program in the background. Then use the user command kill to send SIGUSR1 and SIGUSR2 signals to the main and sibling threads. Record your output using the script command. Verify that each thread handles the receipt of signals properly. Is a signal-catching routine established in one thread shared by the others? Note that in older (2.1 or earlier) kernel implementations SIGUSR1 and SIGUSR2 were used by LinuxThreads for thread management. In these settings try using SIGINT and SIGQUIT in place of SIGUSR1 and SIGUSR2.

Категории