I'm learning to write a signal handler in C for a Linux system. Here's my code:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_handler(int signum){
//Return type of the handler function should be void
printf("\nInside handler function\n");
}
int main(){
signal(SIGINT,sig_handler); // Register signal handler
for(int i=1;;i ){ //Infinite loop
printf("%d : Inside main function\n",i);
sleep(1); // Delay for 1 second
}
return 0;
}
My question is, why when I hit Ctrl C twice, the program stops? Shouldn't it be that everytime I hit Ctrl C the signal handler runs, so effectively the program should run forever?
In reality, this is my output, the signal handler is only called in the first Ctrl C, not the second time:
1 : Inside main function
2 : Inside main function
3 : Inside main function
4 : Inside main function
^C
Inside handler function
5 : Inside main function
6 : Inside main function
7 : Inside main function
8 : Inside main function
9 : Inside main function
10 : Inside main function
^C
CodePudding user response:
On Linux, a number of factors contribute to the behaviour of signal
. Depending on the version of glibc, the defined feature_test_macros(7)
, and whether or not the kernel version of the function is used, the results will differ between two polarizing behaviours, described as System V semantics and BSD semantics:
With System V semantics, when a signal handler is invoked, the disposition of the signal is reset to its default behaviour (SIG_DFL
). Additionally, further delivery of the signal is not blocked during the execution of the signal handler.
With BSD semantics, when a signal handler is invoked, the disposition of the signal is not reset, and further delivery of the signal is blocked during the execution of the signal handler. Additionally, certain system calls will be automatically be restarted if interrupted by the signal handler.
Your version of signal
appears to be supplying System V semantics. The signal disposition is reset after the first signal handler, and the following SIGINT
terminates the program.
See signal(2)
and its notes on portability for more details.
See signal-safety(7)
for a list of functions that are safe to call from within a signal handler. printf
is not an async-signal-safe function, and should not be called from within a signal handler.
Use write(2)
instead.
The quickest fix is to call signal
within the signal handler to once again set the signal disposition to the signal handler.
void sig_handler(int signum)
{
(void) signum;
char msg[] = "Signal handler called!\n";
write(STDOUT_FILENO, msg, strlen(msg));
signal(SIGINT, sig_handler);
}
The more robust solution is to use sigaction(2)
to establish signal handlers, as its behaviour is more consistent and provides better portability.
A basic example:
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
volatile sig_atomic_t sig_count = 0;
void sig_handler(int signum)
{
(void) signum;
sig_count ;
char msg[] = "Signal handler called!\n";
write(STDOUT_FILENO, msg, strlen(msg));
}
int main(void)
{
struct sigaction sa = { .sa_handler = sig_handler };
sigaction(SIGINT, &sa, NULL);
while (sig_count < 5);
}
The default behaviour of sigaction
is similar to that described by BSD semantics, with the exception that certain system calls will not restart when interrupted by the signal handler.
To enable this behaviour, the .sa_flags
member of the struct sigaction
should contain the SA_RESTART
flag.
To mimic System V semantics, the .sa_flags
member of the struct sigaction
should contain the SA_RESETHAND
and SA_NODEFER
flags.
CodePudding user response:
TL;DR: use sigaction()
, not signal()
, to install signal handlers.
As comments and other answers have observed, your signal handler has undefined behavior as a result of its call to printf()
. In the event that the signal handler is ever triggered, that gives the whole program UB, which makes it very difficult to reason about its observed behavior.
But consider this variation instead:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_handler(int signum){
static const char msg[] = "\nInside handler function\n";
write(1, msg, sizeof(msg) - 1);
}
int main(){
signal(SIGINT,sig_handler); // Register signal handler
for(int i=1;;i ){ //Infinite loop
printf("%d : Inside main function\n",i);
sleep(1); // Delay for 1 second
}
return 0;
}
I have switched from printf
to write
inside the signal handler, thereby removing the UB. And if I compile it with
gcc -std=c11 htest.c -o htest
then the resulting executable still exhibits the behavior you describe: the first Ctrl-C is handled, but the second is not.
HOWEVER, if I instead compile with
gcc htest.c -o htest
then the resulting program intercepts every Ctrl-C I type, as I guess you were expecting. So what's going on?
The problem revolves around the fact that the details of the behavior of the signal()
function have varied historically. For more detail, see the portability notes in the signal
(2
) manual page, but here's a brief rundown:
In the original System V UNIX, the custom signal handlers installed via signal()
were one-shots: when such a handler was triggered, the disposition for the signal was reset to its default, and the signal was not blocked during execution of the handler.
The System V behavior has some issues, so BSD implemented signal()
differently in these respects: signal disposition is not automatically reset, and the signal is blocked during execution of the handler. And additionally, in BSD, certain blocking system calls are automatically restarted if interrupted by a signal that does not result in the program terminating.
These differences mean that the only portable uses for the signal()
function are for setting signal disposition to SIG_DFL
or SIG_IGN
.
Glibc supports both alternatives, and which one you get is controlled by feature test macros, which can be influenced by compiler command-line options. It defaults to BSD semantics as long as the _DEFAULT_SOURCE
macro is defined (_BSD_SOURCE
prior to glibc 2.19). Gcc defines that macro by default, but some command line options, notably the strict-conformance -std
options, cause Gcc not to define it. And that's why I could get different behavior depending on how I compiled the program.
On POSIX systems, the solution is to use the sigaction()
function instead of signal()
to register signal handlers. This function has well-defined semantics for all the areas of behavioral difference described above, including a well-defined means to choose among them. However, to get its declaration included in all cases, you will need to ensure that a different feature-test macro is defined. For example:
// Manipulation of feature-test macros should precede all header inclusions
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 1
#endif
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int signum) {
static const char msg[] = "\nInside handler function\n";
write(1, msg, sizeof(msg) - 1);
}
int main(void) {
// Register signal handler
struct sigaction sa = { .sa_handler = sig_handler /* default sa_mask and sa_flags */ };
sigaction(SIGINT, &sa, NULL);
while (int i = 1; ; i ) {
printf("%d : Inside main function\n",i);
sleep(1);
}
return 0;
}
That will reliably get you a handler that is not reset when triggered, does have SIGINT
blocked while it is being handled, and does not automatically restart system calls interrupted by the signal. That will prevent the program from being killed via a SIGINT
, though there are other ways it can be killed, such as via a SIGKILL
(which cannot be blocked or handled).
CodePudding user response:
My question is, why when I hit Ctrl C twice, the program stops?
Undefined behaviour:
Your code invokes undefined behaviour because it calls a function (printf
) that isn't async-signal-safe.¹
From C11:
If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared as volatile sig_atomic_t, or the signal handler calls any function in the standard library other than the abort function, the _Exit function, the quick_exit function, or the signal function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler.
As per the C Standard, you can only call:
abort()
_Exit()
quick_exit()
signal() /* With the first argument equal to the signal number the handler caught */
safely inside a signal handler.
The POSIX standard, however, specifies many more functions. So you can have write()
, but not printf()
.
[1] — An async-signal-safe function is one that can be safely called from within a signal handler. (Linux man page)
CodePudding user response:
Don't use signal()
. Instead use sigaction()
.
Historically, some UNIX flavors reset the signal disposition to the default behavior upon entry into the user-defined signal handler. That meant that your first SIGFOO would invoke your handler, but your next one would trigger SIG_DFL behavior. Importantly, this is consistent with what you observe.
Yes, printf()
is unsafe (undefined behavior) to call in a signal handler. In the code you provide, it might conceivably interrupt itself, which can lead to all sorts of nastiness. In practice, people call printf()
all the time in trivial programs to no great harm. The fact that your behavior is repeatable suggests that you are getting deterministic behavior, and not dreadful eldritch magic undefined behavior.
sigaction()
forces you to specify what kind of behavior you want when the user-supplied handler is invoked, and is thus superior to signal()
.
CodePudding user response:
This happened because in the function sig_handler()
you defined has no call to exit()
or any similar function. If you will add just exit(1);
in that function, it will terminate at the first sigint signal. Also, calling printf()
inside signal handler isn't a safe call async-signal-safe, thanks to Andreas Wenzel.
void sig_handler(int signum)
{
printf("\nInside handler function\n"); // don't use printf in this function
exit(EXIT_FAILURE); // or exit(1) if you don't want to include `stdlib.h`
}