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_handler
s - 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 theSIGINT
, so can I say this approach is fundamentally the same assignal(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 withSIG_IGN
. SIG_IGN
will surviveexec
, 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
How to Build a Linux Kernel Module So That It Is Compatible with All Kernel Releases
Printing an Integer with X86 32-Bit Linux Sys_Write (Nasm)
Remove File Coding Mark But Preserve Its Coding
Matlab Mex Socket Wrapper Library
Backing Up (And Restoring) a Plone Instance
Starting a Shell in the Docker Alpine Container
How to Convert an Image to Grayscale via the Command Line
Split Output of Command by Columns Using Bash
Logo Programming Language Implementations
How to Find All Files with a Filename That Ends with Tilde
Pyqt5 Error "Pycapsule_Getpointer Called with Incorrect Name"
Pipe Bash Command Output to Stdout and to a Variable
How to Get Details of All Modules/Drivers That Were Initialized/Probed During the Linux Kernel Boot
Dyld_Library_Path Environment Variable Is Not Forwarded to External Command in Makefile on MACos
Emulating Slurm on Ubuntu 16.04
Omitting the First Line from Any Linux Command Output