Given this code:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void sigint_handler(int h)
{
printf("Hey! I caught a SIGINT! :)\n");
}
int main()
{
struct sigaction act;
act.sa_handler = &sigint_handler;
if (0 != sigaction(SIGINT, &act, NULL)) {
perror("unable to setup the SIGINT handler!\n");
return 1;
}
while(1) { }
return 0;
}
compiled with gcc 7.2.0
(kernel 4.13.12
) using the following options: -Wall -pedantic -ansi -std=gnu11
.
The first signal is always caught, but sometimes, the second one is not caught, and sometimes it is.
I encountered this bug while spamming Ctrl-C
at process' startup.
What did I miss to catch all signals?
As Martin James observed in a comment:
Your SIGINT handler has only one line - a call to a function that is not async-signal safe:(
Somewhat later, I observed:
You've no idea what the other fields in the
struct sigaction
are set to because you didn't initializeact
. Maybe you will get better behaviour if you set the documented fields to known values. You can usewrite()
in a POSIX signal handler (not in standard C, but fortunately you're not using standard C). You shouldn't useprintf()
, though in this context it is unlikely to cause any trouble. One minor advantage ofwrite()
— there's no application level buffering to worry about.
The question How to avoid using printf()
in a signal handler discusses which functions can be used in a signal handler. Note that the functions from the (This was accurate for POSIX 2008. One of the changes in POSIX 2016 is that a number of signal-safe routines have been added to the list in Signal Concepts, including both <string.h>
header such as strlen()
and strchr()
are not listed amongst those that are async-signal safe. I find that omission puzzling, but that's what POSIX (2008 and earlier) says.strlen()
and strchr()
— this makes a lot of sense to me.)
I adapted your code like this:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static void sigint_handler(int h)
{
char message[] = "Hey! I caught a SIGINT x! :\n";
char *x = message;
while (*x != 'x' && *x != '\0')
x++;
if (*x != '\0')
*x = (h % 10) + '0';
write(1, message, sizeof(message) - 1);
}
int main(void)
{
struct sigaction act = { 0 };
act.sa_handler = &sigint_handler;
if (0 != sigaction(SIGINT, &act, NULL))
{
perror("Unable to setup the SIGINT handler!\n");
return 1;
}
while (1)
{
printf("Pausing for a moment...\n");
pause();
printf("You interrupted my dozing\n");
}
return 0;
}
The code uses pause()
rather than spinning in a busy-loop. It's an unusual system call; it never returns normally (the exec*()
family of functions never return normally either).
I compile with stringent warning options:
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
> -Wstrict-prototypes sig13.c -o sig13
$
If I didn't use h
(the argument to the signal handler), the code wouldn't compile, so I used it. The code avoids using string handling functions (char *x = strchr(message, 'x'); if (x != 0) *x = (h % 10) + '0';
would be clearer). The function is static
because it won't be used outside this file — so there isn't a header to declare it.
When executed (on a Mac running macOS High Sierra 10.13.2, using GCC 7.2.0), it produces output like:
$ ./sig13
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^\Quit: 3
$
The main moral to this is "make sure your variables are properly initialized". A secondary moral is to make sure your signal handler is clean.