The sigwait library API

Let's start with the sigwait(3):

include <signal.h>
int sigwait(const sigset_t *set, int *sig);

The sigwait(3) library API allows a process (or thread) to block, wait, until any  signal in the signal-set set is pending delivery to it. The moment a signal arrives, the sigwait is unblocked; the particular signal that arrived, its integer value, is placed in the value-result second parameter sig. Under the hood, the sigwait removes the signal just delivered from the process (or thread) pending mask.

Thus, the sigwait(3) is advantageous to the pause(2) by virtue of the following:

  • You can wait upon the delivery of particular signals to the process 
  • When one of those signals is delivered, its value is known

The return value from sigwait(3) is 0 on success and a positive value on error (note that it being a library API, errno remains unaffected). (Internally, the sigwait(3) is implemented via the sigtimedwait(2) API.)

However, things are not always as simple as they appear at first glance. The reality is that there are a couple of important points to consider:

  • A risky situation called a race can be set up if the signals one intends waiting upon are not first blocked by the calling process. (Technically, this is as there is a window of opportunity between a signal being delivered to the process and the sigwait call initializing). Once running, though, the sigwait will atomically unblock the signals, allowing them to be delivered upon the caller process.
  • What if a signal (one within the signal set we define), is also trapped (caught) via either the sigaction(2) or signal(2) API, AND via the sigwait(3) API? In such a scenario, the POSIX standard states that it is up to the implementation to decide how to handle the delivered signal; Linux seems  to favor handling the signal via the sigwait(3). (This makes sense: if a process  issues the sigwait API, the process blocks on signals. If a signal does become pending (meaning, it has just been delivered) on the process, then the sigwait API sucks in or consumes the signal: it is now no longer pending delivery on the process, and thus cannot be caught via signal handlers set up via the sigaction(2) or signal(3) APIs.)

To test this, we write a small application ch12/sigwt/sigwt.c as well as a shell script ch12/sigwt/bombard.sh to shower all signals upon it. (The reader will find the code within the book's GitHub repository, as always; this time, we leave it as an exercise to the reader to study the source, and experiment with it.) A couple of sample runs follow:

In one Terminal window, we run our sigwt program as follows:

$ ./sigwt 
Usage: ./sigwt 0|1
0 => block All signals and sigwait for them
1 => block all signals except the SIGFPE and SIGSEGV and sigwait
(further, we setup an async handler for the SIGFPE, not the SIGSEGV)
$ ./sigwt 0
./sigwt: All signals blocked (and only SIGFPE caught w/ sigaction)
[SigBlk: 1 2 3 4 5 6 7 8 10 11 12 13 14 15 16 17 18 20 21 22 23 24 25 26 27 28 29 30 31 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 ]
./sigwt: waiting upon signals now ...

Note how we have first blocked all signals (via the sigprocmask(2); we invoke our generic  common.c:show_blocked_signals() function to display all currently blocked signals in the process signal mask; as expected, all are blocked, with the obvious exception of signal numbers 9, 19, 32, and 33 (why?)). Recall that, once running, the sigwait(3) will atomically unblock the signals, allowing them to be delivered upon the caller.

In another Terminal window, run the shell script; the script's job is simple: it sends (via kill(1)) every signal—from 1 to 64, except for SIGKILL (9), SIGSTOP (19), 32, and 33—the two RT signals reserved for use by the pthreads framework:

$ ./bombard.sh $(pgrep sigwt) 1
Sending 1 instances each of ALL signals to process 2705
1 2 3 4 5 6 7 8 10 11 12 13 14 15 16 17 18 20 21 22 23 24 25 26 27 28 29 30 31 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
$

In the original window, we observe the output:

Received signal# 1
Received signal# 2
Received signal# 3
Received signal# 4
Received signal# 5
Received signal# 6
Received signal# 7
Received signal# 8
Received signal# 10
Received signal# 11
[...]
Received signal# 17
Received signal# 18
Received signal# 20
Received signal# 21
[...]
Received signal# 31
Received signal# 34
Received signal# 35
Received signal# 36
Received signal# 37
[...]
Received signal# 64

All delivered signals were processed via the sigwait! Including the SIGFPE (# 8) and the SIGSEGV (# 11). This is as they were synchronously sent by another process (the shell script) and not by the kernel.

A quick pkill(1) kills off the sigwt process (as if one needs reminding: SIGKILL and SIGSTOP cannot be masked):

pkill -SIGKILL sigwt 

Now for the next test case, running it with option 1:

$ ./sigwt 
Usage: ./sigwt 0|1
0 => block All signals and sigwait for them
1 => block all signals except the SIGFPE and SIGSEGV and sigwait
(further, we setup an async handler for the SIGFPE, not the SIGSEGV)
$ ./sigwt 1

./sigwt: removing SIGFPE and SIGSEGV from the signal mask...
./sigwt: all signals except SIGFPE and SIGSEGV blocked
[SigBlk: 1 2 3 4 5 6 7 10 12 13 14 15 16 17 18 20 21 22 23 24 25 26 27 28 29 30 31 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 ]
./sigwt: waiting upon signals now ...

Note how signal numbers 8 (SIGFPE) and 11 (SIGSEGV) are not among the rest that are now blocked (besides the usual suspects, 9, 19, 32, 33). Recall that, once running, the sigwait(3) will atomically unblock the signals, allowing them to be delivered upon the caller.

In another Terminal window, run the shell script:

$ ./bombard.sh $(pgrep sigwt) 1
Sending 1 instances each of ALL signals to process 13759
1 2 3 4 5 6 7 8 10 11 ./bombard.sh: line 16: kill: (13759) - No such process
bombard.sh: "kill -12 13759" failed, loop count=1
$

In the original window, we observe the output:

Received signal# 1
Received signal# 2
Received signal# 3
Received signal# 4
Received signal# 5
Received signal# 6
Received signal# 7
*** siggy: handled SIGFPE (8) ***
Received signal# 10
Segmentation fault (core dumped)
$

As we trapped the SIGFPE (via sigaction(2)), it was handled; however, the uncaught SIGSEGV of course causes the process to die abnormally. Not very pleasant at all.

A little tinkering with the code reveals an interesting aspect; the original code snippet is this:

[...]
if (atoi(argv[1]) == 1) {
/* IMP: unblocking signals here removes them from the influence of
* the sigwait* APIs; this is *required* for correctly handling
* fatal signals from the kernel.
*/
printf("%s: removing SIGFPE and SIGSEGV from the signal mask... ", argv[0]);
sigdelset(&set, SIGFPE);
#if 1
sigdelset(&set, SIGSEGV);
#endif
[...]

What if we effectively block the SIGSEGV by changing the preceding #if 1 to #if 0? Let's do so, rebuild, and retry:

[...]
Received signal# 1
Received signal# 2
Received signal# 3
Received signal# 4
Received signal# 5
Received signal# 6
Received signal# 7
*** siggy: handled SIGFPE (8) ***
Received signal# 10
Received signal# 11
Received signal# 12
[...]

This time the SIGSEGV is processed via the sigwait! Yes, indeed; but only because it was artificially generated by a process, and not sent by the OS. 

So, as usual, there's more to it: how exactly signal handling happens is determined by the following:

  • Whether or not the process blocks the signal prior to calling sigmask (or variants)
  • With regard to fatal signals (such as SIGILL, SIGFPE, SIGSEGV, SIGBUS, and so on), how the  signal is generated matters: artificially, via just a process (kill(2)) or actually generated via the kernel (due to a bug of some sort)
  • We find the following:
    • If the signal is blocked by the process before invoking the sigwait, then, if the signal is delivered artificially via kill(2) (or variants), the sigwait will get unblocked upon delivery of the signal and the application developer can handle the signal.
    • However, if the fatal signal is delivered via the OS due to a bug, then, whether or not the process blocks it, the default action takes place, abruptly (and disgracefully) killing the process! This is probably not what one wants; thus, we conclude that it's better to trap fatal signals like the preceding via the usual asynchronous sigaction(2) style and not via the sigwait (or variants thereof).
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset