1

I've been looking at the signal handling in a library I am using in a project of my own {Joan's pigpio as it happens} but having read the Raspbian man pages for signal(7), sigaction(2) and finally signal(2), I am trying to determine what happens if a custom signal handler is installed and the process receives a signal of the specified type. Under the following heading of the signal(2) manpage is:

Portability

The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN. The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose. POSIX.1 solved the portability mess by specifying sigaction(2), which provides explicit control of the semantics when a signal handler is invoked; use that interface instead of signal(). In the original UNIX systems, when a handler that was established using signal() was invoked by the delivery of a signal, the disposition of the signal would be reset to SIG_DFL, and the system did not block delivery of further instances of the signal. This is equivalent to calling sigaction(2) with the following flags:

sa.sa_flags = SA_RESETHAND | SA_NODEFER;

System V also provides these semantics for signal(). This was bad because the signal might be delivered again before the handler had a chance to reestablish itself. Furthermore, rapid deliveries of the same signal could result in recursive invocations of the handler.

BSD improved on this situation, but unfortunately also changed the semantics of the existing signal() interface while doing so. On BSD, when a signal handler is invoked, the signal disposition is not reset, and further instances of the signal are blocked from being delivered while the handler is executing. Furthermore, certain blocking system calls are automatically restarted if interrupted by a signal handler (see signal(7)). The BSD semantics are equivalent to calling sigaction(2) with the following flags:

sa.sa_flags = SA_RESTART;

The situation on Linux is as follows:

  • The kernel's signal() system call provides System V semantics.
  • By default, in glibc 2 and later, the signal() wrapper function does not invoke the kernel system call. Instead, it calls sigaction(2) using flags that supply BSD semantics. This default behavior is provided as long as the _BSD_SOURCE feature test macro is defined. By default, _BSD_SOURCE is defined; it is also implicitly defined if one defines _GNU_SOURCE, and can of course be explicitly defined.
  • On glibc 2 and later, if the _BSD_SOURCE feature test macro is not defined, then signal() provides System V semantics. (The default implicit definition of _BSD_SOURCE is not provided if one invokes gcc(1) in one of its standard modes (-std=xxx or -ansi) or defines various other feature test macros such as _POSIX_SOURCE, _XOPEN_SOURCE, or _SVID_SOURCE; see feature_test_macros(7).)

Given that persistent signal handlers are those on systems where a handler is NOT reset to SIG_DFL on receipt of that signal (and non-persistent ones are those that are):

Are the handlers persistent on RPis?

Does it depend on those feature test macros when the library and/or my project is (re)compiled OR, perhaps, what they were when the kernel was compiled?

2

As flakeshake pointed out and then deleted for some reason, this is only an issue if you intentionally want to do things the wrong way. All of the documents you refer to make it very clear the point here is don't use signal(). It exists because it's part of ANSI/ISO C, i.e., it has to for compatibility with ancient code. To be blunt: If it is too hard for you to just use sigaction(), then how is it possible for you to care about the semantics of what you are doing (the wrong way, on purpose)?

Anyhow, this is easy enough to demonstrate.

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

void handler (int s __attribute__((unused))) {
    write(2, "Hello world.\n", 13);
}

int main (void) 
{
    printf("PID %d\n", (int)getpid());
    signal(SIGUSR1, handler);
    while(1) sleep(1);
    puts("Goodbye!");
    return 1;
}    

Unhandled signals mostly terminate a process. Hopefully what that code does is extremely clear.1 So let's compile it using no switches:

 gcc test.c

Run it, then from somewhere else, repeatedly kill -s SIGUSR1 [whatever pid]. You get:

Hello world.
Hello world.

Over and over. Now compile it gcc --std=c89 test.c and do the same thing. You get:

Hello world.
“./a.out” terminated by signal SIGUSR1 (User defined signal 1)

As stated, the handler was reset because you told the compiler that's the behavior you want.

Does it depend on those feature test macros when the library and/or my project is (re)compiled OR, perhaps, what they were when the kernel was compiled?

Well, it's implied that _BSD_SOURCE is effectively set/used by default (in the library). Linux unofficially aims for POSIX compliance, which doesn't help here much because POSIX just says the behaviour on this is implementation defined, probably because POSIX does include sigaction(), which is what you are explicitly supposed to be using if you are concerned about standards, the predictable behavior defined in standards (and redefined and refined in later standards such as POSIX), etc. If not, then who cares. Use the wrong function and leave it up to the implementation.

Sort of interestingly, trying to undefine _BSD_SOURCE:

#undef _BSD_SOURCE

Does not result in the non-persistent handlers that --std=c89 does. Of course, it doesn't say anywhere that it would, it just refers to the "default implicit definition" of _BSD_SOURCE used by GCC/glibc.

Generally speaking "feature test macros" that invoke non-default behaviour work if you define them for a specific compilation unit.


1. As SlySven points out, using fprintf in a signal handler is not a good practice; see the list of "Async-signal-safe functions" in man 7 signal about this. Using magic number 2 instead of fileno(stderr) is not a great practice either, but since fileno() isn't async safe here it better demonstrates the principle. See comments below for more discussion.

| improve this answer | |
  • I was wondering whether the behaviour is set (possibly by the feature macro) when the kernel is compiled though? Of course I am recompiling the library, and of course I/joan are using sigaction() rather than signal(), it is just the documentation for the latter is pertinent...! – SlySven Apr 11 '16 at 13:36
  • Oh and for the record your test case is violating another thing in that it is using a printf() family function in the handler which I do not think is safe because it indirectly allocates memory off the heap - which is not something that can be done atomically...! – SlySven Apr 11 '16 at 13:45
  • Well, valgrind doesn't seem to think there's anything wrong with it -- although if that weren't a compiled in constant I guess you could play games to see what the state of some global string turns out to be at the time. – goldilocks Apr 11 '16 at 14:13
  • I actually stumbled across a site which included a section on signals which is very informative about all sorts of good (and bad) C/POSIX coding practices and explains why they are so. The issue with the example comes under heading SIG30-C! – SlySven Apr 11 '16 at 14:28
  • Point taken! TBH I don't use them for anything except ignoring things. Actually there is a long list of "Async-signal-safe functions" in man 7 signal but predictably they don't include much in the way of I/O -- except for write() and read(), so there is a "safe and compliant version of this" (maybe I'll edit in a reference to these comments). You can also fork()/exec(), so you could use a handler to do a hard reset, etc. – goldilocks Apr 11 '16 at 14:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.