How to Handle SIGINT Signal in C
-
Use
signal
Function to RegisterSIGINT
Signal Handler Routine -
Use
sigaction
Function to RegisterSIGINT
Signal Handler Routine
This article will demonstrate multiple methods about how to handle the SIGINT
signal in C.
Use signal
Function to Register SIGINT
Signal Handler Routine
SIGINT
is one of the predefined signals that’s associated with the terminal interrupt character (commonly Ctrl+C). It causes the shell to stop the current process and return to its main loop, displaying a new command prompt to the user. Note that signals are just small notifications sent between processes inside the kernel and the userspace. They are sometimes called software interrupts as they usually stop the normal execution of the program and execute the special action for the given signal type. The actions are mostly defined as a default across the systems, but the user can implement a special function and register it as new action for the signal.
Mind though, some signals have strictly fixed behavior assigned from the operating system and can’t be overridden, as the kernel uses them to do critical things like terminate unresponsive processes.
SIGINT
, though, is the kind of signal that can be handled, meaning that the user may register a custom function to be executed when the process receives the signal. The default action of the SIGINT
signal is for the process to terminate. In the following example code, we implement a program that executes an infinite while
loop, calling the fprintf
function continuously inside it. Although, just before the loop is started, we call the signal
function to register the SIGINT
sigintHandler
function as a handler, which has only one function call, printing a string to the stdout
.
Note that write
is used instead of printf
because the signal handler code must not call non-reentrant functions that modify the global program data underneath the hood. To demonstrate the example, you should run the program and then send the SIGINT
signal from the other terminal to observe the behavior. Usually, it should stop executing the while
loop, print the "Caught SIGINT!"
string and exit with success status code.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
static void sigintHandler(int sig) {
write(STDERR_FILENO, "Caught SIGINT!\n", 15);
}
int main(int argc, char *argv[]) {
if (signal(SIGINT, sigintHandler) == SIG_ERR) errExit("signal SIGINT");
while (1) {
fprintf(stderr, "%d", 0);
sleep(3);
}
exit(EXIT_SUCCESS);
}
Use sigaction
Function to Register SIGINT
Signal Handler Routine
Even though modern implementations of the signal
function call in UNIX systems work reliably for simple use cases, it’s recommended to utilize the sigaction
function for registering the signal handlers. It offers vastly more options compared to the signal
call, but it also provides core features that are necessary for any serious use case of the signals. sigaction
takes special struct sigaction
arguments to specify the handler function pointer and other indicators with it. In this case, we implement a scenario where a child process executes a while
loop with a global variable shutdown_flag
as a condition expression while the parent waits for it. Note that, shutdown_flag
variable is of type sig_atomic_t
, a special integer that can be safely modified globally from the signal handler code. So, once the user sends SIGINT
signal to the child process, the cleanupRoutine
function gets invoked that sets the shutdown_flag
to 0
value and the control returns to the while
loop where condition expression is evaluated again and the zero forces it to break from the loop. The child exits and the parent gets its status as the waitpid
function returns.
#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
volatile sig_atomic_t shutdown_flag = 1;
void cleanupRoutine(int signal_number) { shutdown_flag = 0; }
int main(void) {
int wstatus;
pid_t c_pid = fork();
if (c_pid == -1) errExit("fork");
if (c_pid == 0) {
printf("printed from child process - %d\n", getpid());
int count = 0;
struct sigaction sigterm_action;
memset(&sigterm_action, 0, sizeof(sigterm_action));
sigterm_action.sa_handler = &cleanupRoutine;
sigterm_action.sa_flags = 0;
// Mask other signals from interrupting SIGINT handler
if (sigfillset(&sigterm_action.sa_mask) != 0) errExit("sigfillset");
// Register SIGINT handler
if (sigaction(SIGINT, &sigterm_action, NULL) != 0) errExit("sigaction");
while (shutdown_flag) {
getpid();
}
printf("pid: %d exited\n", getpid());
exit(EXIT_SUCCESS);
} else {
printf("printed from parent process - %d\n", getpid());
int ret;
if (waitpid(c_pid, &wstatus, WUNTRACED) == -1) errExit("waitpid");
}
exit(EXIT_SUCCESS);
}
Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.
LinkedIn Facebook