What is the difference between sigaction and signal?
To me, this below line was enough to decide:
The sigaction() function provides a more comprehensive and reliable mechanism for controlling signals; new applications should use sigaction() rather than signal()
http://pubs.opengroup.org/onlinepubs/009695399/functions/signal.html#tag_03_690_07
Whether you're starting from scratch or modifying an old program, sigaction should be the right option.
They're different interfaces for OS's signal facilities. One should prefer using sigaction to signal if possible as the signal() has implementation-defined (often race prone) behavior and behaves differently on Windows, OS X, Linux and other UNIX systems.
See this security note for details.
Use sigaction()
unless you've got very compelling reasons not to do so.
The signal()
interface has antiquity (and hence availability) in its favour, and it is defined in the C standard. Nevertheless, it has a number of undesirable characteristics that sigaction()
avoids - unless you use the flags explicitly added to sigaction()
to allow it to faithfully simulate the old signal()
behaviour.
- The
signal()
function does not (necessarily) block other signals from arriving while the current handler is executing;sigaction()
can block other signals until the current handler returns. - The
signal()
function (usually) resets the signal action back toSIG_DFL
(default) for almost all signals. This means that thesignal()
handler must reinstall itself as its first action. It also opens up a window of vulnerability between the time when the signal is detected and the handler is reinstalled during which if a second instance of the signal arrives, the default behaviour (usually terminate, sometimes with prejudice - aka core dump) occurs. - The exact behaviour of
signal()
varies between systems — and the standards permit those variations.
These are generally good reasons for using sigaction()
instead of signal()
. However, the interface of sigaction()
is undeniably more fiddly.
Whichever of the two you use, do not be tempted by the alternative signal interfaces such as
sighold()
,
sigignore()
,
sigpause()
and
sigrelse()
.
They are nominally alternatives to sigaction()
, but they are only barely standardized and are present in POSIX for backwards compatibility rather than for serious use. Note that the POSIX standard says their behaviour in multi-threaded programs is undefined.
Multi-threaded programs and signals is a whole other complicated story. AFAIK, both signal()
and sigaction()
are OK in multi-threaded applications.
Cornstalks observes:
The Linux man page for
signal()
says:
The effects of
signal()
in a multi-threaded process are unspecified.Thus, I think
sigaction()
is the only that can be used safely in a multi-threaded process.
That's interesting. The Linux manual page is more restrictive than POSIX in this case. POSIX specifies for signal()
:
If the process is multi-threaded, or if the process is single-threaded and a signal handler is executed other than as the result of:
- The process calling
abort()
,raise()
,kill()
,pthread_kill()
, orsigqueue()
to generate a signal that is not blocked- A pending signal being unblocked and being delivered before the call that unblocked it returns
the behavior is undefined if the signal handler refers to any object other than
errno
with static storage duration other than by assigning a value to an object declared asvolatile sig_atomic_t
, or if the signal handler calls any function defined in this standard other than one of the functions listed in Signal Concepts.
So POSIX clearly specifies the behaviour of signal()
in a multi-threaded application.
Nevertheless, sigaction()
is to be preferred in essentially all circumstances — and portable multi-threaded code should use sigaction()
unless there's an overwhelming reason why it can't (such as "only use functions defined by Standard C" — and yes, C11 code can be multi-threaded). Which is basically what the opening paragraph of this answer also says.
In short:
sigaction()
(see here and here) is good and well-defined, but is a POSIX function and so it works only on Linux or POSIX systems. signal()
(see here and here) is bad and poorly-defined, but is a C standard function and so it works on anything.
What do the Linux man pages have to say about it?
man 2 signal
(see it online here) states:
The behavior of
signal()
varies across UNIX versions, and has also varied historically across different versions of Linux. Avoid its use: usesigaction(2)
instead. See Portability below.
Portability
The only portable use of
signal()
is to set a signal's disposition toSIG_DFL
orSIG_IGN
. The semantics when usingsignal()
to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.
In other words: do not use signal()
. Use sigaction()
instead!
This position is reiterated in the next line which states (emphasis added):
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 ofsignal()
.
What does GCC think?
From https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling (emphasis added):
Compatibility Note: As said above for
signal
, this function should be avoided when possible.sigaction
is the preferred method.
So, if both Linux and GCC say not to use signal()
, but to use sigaction()
instead, that begs the question: how the heck do we use this confusing sigaction()
thing!?
Usage Examples:
Read GCC's EXCELLENT signal()
example here: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
And their EXCELLENT sigaction()
example here: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
After reading those pages, I came up with the following technique for sigaction()
:
1. sigaction()
, since it's the right way to attach a signal handler, as described above:
#include <errno.h> // errno
#include <signal.h> // sigaction()
#include <stdio.h> // printf()
#include <string.h> // strerror()
// Format: const char *, unsigned int, const char *
#define LOG_LOCATION __FILE__, __LINE__, __func__
#define LOG_FORMAT_STR "file: %s, line: %u, func: %s: "
/// @brief Callback function to handle termination signals, such as
/// Ctrl + C
/// @param[in] signal Signal number of the signal being handled by this
/// callback function
/// @return None
static void termination_handler(const int signal)
{
switch (signal)
{
case SIGINT:
printf("\nSIGINT (%i) (Ctrl + C) signal caught.\n", signal);
break;
case SIGTERM:
printf("\nSIGTERM (%i) (default `kill` or `killall`) signal caught.\n",
signal);
break;
case SIGHUP:
printf("\nSIGHUP (%i) (\"hang-up\") signal caught.\n", signal);
break;
default:
printf("\nUnk signal (%i) caught.\n", signal);
break;
}
// DO PROGRAM CLEANUP HERE, such as freeing memory, closing files, etc.
exit(signal);
}
/// @brief Set a new signal handler action for a given signal
/// @details Only update the signals with our custom handler if they are NOT
/// set to "signal ignore" (`SIG_IGN`), which means they are currently
/// intentionally ignored. GCC recommends this "because non-job-control
/// shells often ignore certain signals when starting children, and it is
/// important for children to respect this." See
/// https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
/// and
/// https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html.
/// Note that termination signals can be found here:
/// https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
/// @param[in] signal Signal to set to this action
/// @param[in] action Pointer to sigaction struct, including the callback
/// function inside it, to attach to this signal
/// @return None
static inline void set_sigaction(int signal, const struct sigaction *action)
{
struct sigaction old_action;
// check current signal handler action to see if it's set to SIGNAL IGNORE
sigaction(signal, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
{
// set new signal handler action to what we want
int ret_code = sigaction(signal, action, NULL);
if (ret_code == -1)
{
printf(LOG_FORMAT_STR "sigaction failed when setting signal to "
"%i; errno = %i: %s\n",
LOG_LOCATION, signal, errno, strerror(errno));
}
}
}
int main(int argc, char *argv[])
{
//...
// Register callbacks to handle kill signals; prefer the Linux function
// `sigaction()` over the C function `signal()`: "It is better to use
// sigaction if it is available since the results are much more reliable."
// Source:
// https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
// and
// https://stackoverflow.com/questions/231912/what-is-the-difference-between-sigaction-and-signal/232711#232711.
// See here for official gcc `sigaction()` demo, which this code is modeled
// after:
// https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
// Set up the structure to specify the new action, per GCC's demo.
struct sigaction new_action;
new_action.sa_handler = termination_handler; // set callback function
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
// SIGINT: ie: Ctrl + C kill signal
set_sigaction(SIGINT, &new_action);
// SIGTERM: termination signal--the default generated by `kill` and
// `killall`
set_sigaction(SIGTERM, &new_action);
// SIGHUP: "hang-up" signal due to lost connection
set_sigaction(SIGHUP, &new_action);
//...
}
2. And for signal()
, even though its not a good way to attach a signal handler, as described above, it's still good to know how to use it.
Here's the GCC demonstration code copy-pasted, as it's about as good as it's going to get:
#include <signal.h>
void
termination_handler (int signum)
{
struct temp_file *p;
for (p = temp_file_list; p; p = p->next)
unlink (p->name);
}
int
main (void)
{
…
if (signal (SIGINT, termination_handler) == SIG_IGN)
signal (SIGINT, SIG_IGN);
if (signal (SIGHUP, termination_handler) == SIG_IGN)
signal (SIGHUP, SIG_IGN);
if (signal (SIGTERM, termination_handler) == SIG_IGN)
signal (SIGTERM, SIG_IGN);
…
}
The main links to be aware of:
- Standard Signals: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
- Termination Signals: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
- Basic Signal Handling, including official GCC
signal()
usage example: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling - Official GCC
sigaction()
usage example: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html - Signal sets, including
sigemptyset()
andsigfillset()
; I still don't understand these exactly, but know they are important: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html
See also:
- My answer on "How to manually send any signal to any running process" and "How to trap any signal in your program" (ex: in Bash).
- TutorialsPoint C++ Signal Handling [with excellent demo code]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
- https://www.tutorialspoint.com/c_standard_library/signal_h.htm