Explicitly Invoke Sig_Dfl/Sig_Ign Handlers on Linux

Explicitly invoke SIG_DFL/SIG_IGN handlers on Linux

As you discovered you cannot invoke SIG_DFL and SIG_IGN per se. However, you can more-or-less mimic their behavior.

Briefly, imitating normal signal disposition would be:

  • quite easy for user-defined sa_handlers
  • easy enough for SIG_IGN, with the caveat that you'd need to waitpid() in the case of CHLD
  • straightforward but unpleasant for SIG_DFL, re-raising to let the kernel do its magic.

Does this do what you want?

#include <signal.h>
#include <stdlib.h>

/* Manually dispose of a signal, mimicking the behavior of current
* signal dispositions as best we can. We won't cause EINTR, for
* instance.
*
* FIXME: save and restore errno around the SIG_DFL logic and
* SIG_IGN/CHLD logic.
*/
void dispatch_signal(const int signo) {
int stop = 0;
sigset_t oset;
struct sigaction curact;

sigaction(signo, NULL, &curact);

/* SIG_IGN => noop or soak up child term/stop signals (for CHLD) */
if (SIG_IGN == curact.sa_handler) {
if (SIGCHLD == signo) {
int status;
while (waitpid(-1, &status, WNOHANG|WUNTRACED) > 0) {;}
}
return;
}

/* user defined => invoke it */
if (SIG_DFL != curact.sa_handler) {
curact.sa_handler(signo);
return;
}

/* SIG_DFL => let kernel handle it (mostly).
*
* We handle noop signals ourselves -- "Ign" and "Cont", which we
* can never intercept while stopped.
*/
if (SIGURG == signo || SIGWINCH == signo || SIGCONT == signo) return;

/* Unblock CONT if this is a "Stop" signal, so that we may later be
* woken up.
*/
stop = (SIGTSTP == signo || SIGTTIN == signo || SIGTTOU == signo);
if (stop) {
sigset_t sig_cont;

sigemptyset(&sig_cont);
sigaddset(&sig_cont, SIGCONT);
sigprocmask(SIG_UNBLOCK, &sig_cont, &oset);
}

/* Re-raise, letting the kernel do the work:
* - Set exit codes and corefiles for "Term" and "Core"
* - Halt us and signal WUNTRACED'ing parents for "Stop"
* - Do the right thing if we forgot to handle any special
* signals or signals yet to be introduced
*/
kill(getpid(), signo);

/* Re-block CONT, if needed */
if (stop) sigprocmask(SIG_SETMASK, &oset, NULL);
}

UPDATE
(in response to OP's excellent questions)

1: does this slot in after the sigwaitinfo?

Yes. Something like:

... block signals ...
signo = sigwaitinfo(&set, &info);
dispatch_signal(signo);

2: Why not raise those signals handled by SIG_IGN, they'll be ignored anyway

It's slightly more efficient to noop in userspace than by three syscalls (re-raise, unmask, re-mask). Moreover, CHLD has special semantics when SIG_IGNored.

3: Why treat SIGCHLD specially?

Originally (check answer edits) I didn't -- re-raised it in the SIG_IGN case,
because IGNored CHLD signals tell the kernel to automatically reap children.

However, I changed it because "natural" CHLD signals carry information about
the terminated process (at least PID, status, and real UID).
User-generated CHLD signals don't carry the same semantics, and, in my testing,
IGNoring them doesn't cause 2.6 to autoreap queued zombies whose SIGCHLD
was "missed." So, I do it myself.

4: Why are "stop" related signals unblocking CONT. Will not invoking the default handler for CONT unstop the process?

If we're stopped (not executing) and CONT is blocked, we will never receive the
signal to wake us up!

5: Why not call raise instead of the kill line you've given?

Personal preference; raise() would work, too.

different ways to ignore a signal?


if I want to ignore SIGINT signal, then I just need to simply code as: signal(SIGINT, SIG_IGN);, is my understanding corrct?

Yes, but the docs for signal recommend using sigaction. signal's semantics vary by system, while sigaction's are more consistent.

does the compiler automacially inject this handler into the compiled code?

No. The compiler doesn't create an empty handler for SIG_IGN. It literally tells the OS to ignore the signal for the process. It's not a function pointer but a value signal treats specially.

since my own sigint_handler does nothing, it is pretty much like ignoring the SIGINT, so can I say this approach is fundamentally the same as signal(SIGINT, SIG_IGN);?

While both effectively ignore the signal, there are differences.

  • When using a signal handler, a blocking syscall might return prematurely with error EINTR to allow the signal handler to run. This won't happen with SIG_IGN.
  • SIG_IGN will survive exec, but a handler doesn't.
  • On some systems, the signals's disposition is reset to SIG_DFL when the signal handler is called, so your code would only ignore the first instance of the signal on such systems.
  • There are other differences, which may vary by platform.

calling signal handler again in itself

The problem is that the behaviour of signal() varies across UNIX versions, and has also varied historically across different versions of Linux (quoted from Linux man). Especially:

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;

So in such a system you have to call signal() again after a signal has been delivered. Because of these portability issues, the man page starts with:

The behavior of signal() varies across UNIX versions, and has also varied historically across different versions of Linux. Avoid its use:
use sigaction(2) instead. See Portability below.

What is the use of ignoring `SIGCHLD` signal with `sigaction(2)`?

The default behavior of SIGCHLD is to discard the signal, but the child process is kept as a zombie until the parent calls wait() (or a variant) to get its termination status.

But if you explicitly call sigaction() with the disposition SIG_IGN, that causes it not to turn the child into a zombie -- when the child exits it is reaped immediately. See https://stackoverflow.com/a/7171836/1491895

The POSIX way to get this behavior is by calling sigaction with handler = SIG_DFL and flags containing SA_NOCLDWAIT. This is in Linux since 2.6.

I am generating signal and facing the strange behaviour

Here is the code which I have written with your help which is working perfectly fine and I believe this will work nicely in each and every platform

#define _GNU_SOURCE

void Handler(int sig ){
printf("Inside Handler\n");
}

int main(int argc, char *argv[], char *envp[] ){
sighandler_t Action =Handler;

if( Action == SIG_DFL ){
Action = Handler;
}

if( signal(SIGINT, Action ) == SIG_ERR )
exit(EXIT_FAILURE );

for(size_t k = 0 ; ; k++ ){
printf("%d\n", k );
sleep(2);
}

}

Correct way to use signal handlers

The signal manual says:

Finally, if the handler is set to a function sighandler then first either the handler is reset to SIG_DFL or an implementation-dependent blocking of the signal is performed and next sighandler is called with argument signum.

The repeat call to signal is used to reinstall the custom handler after it (might) have been reset to SIG_DFL.



Related Topics



Leave a reply



Submit