How to Handle SIGINT Signal in C

Jinku Hu Feb 02, 2024
  1. Use signal Function to Register SIGINT Signal Handler Routine
  2. Use sigaction Function to Register SIGINT Signal Handler Routine
How to Handle SIGINT Signal in C

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);
}
Author: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

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

Related Article - C Signal