Dr. Roger Ianjamasimanana

The signal.h header for handling signals in C

By Dr. Roger Ianjamasimanana

1. What is the signal.h header in C?

The signal.h header file provides the necessary tools to capture and handle various signals that may occur during the execution of a C program. This page covers predefined types and constants, as well as core functions that allow you to specify either a default or custom handling procedure for different signals.


2. Predefined types

  • sig_atomic_t: represents an atomic type for safely managing a variable that might be accessed by both the main program and a signal handler. Operations on this type are guaranteed to be indivisible (atomic).

For more information, see the example of a typical use of sig_atomic_t.


3. Predefined constants

Several constants, defined in signal.h, serve to specify how to handle signals or indicate various signal types. Below are some notable examples:

Constant Description
SIG_DFL Requests the default handling for a particular signal.
SIG_IGN Indicates that the signal should be ignored.
SIG_ERR Return value from the signal function if an error occurs.
SIGABRT Represents an abnormal termination (possibly triggered by calling abort).
SIGFPE Indicates an invalid arithmetic operation (division by zero, overflow, etc.).
SIGILL Raised when the processor detects an invalid machine instruction.
SIGINT Interactive signal, often sent by the user (for instance, Ctrl + C).
SIGSEGV Indicates illegal memory access (segmentation fault).
SIGTERM Sent to request the program to terminate (a “graceful” shutdown).

4. Signal handling functions in C

4.1. The signal function

// Prototype
void (*signal(int signum, void (*handler)(int)))(int);

The signal function associates the behavior handler with the signal numbered signum. That behavior can be:

  • An “ordinary” user-defined function taking an int parameter.
  • SIG_IGN, meaning that the given signal is ignored.
  • SIG_DFL, applying the default action for that signal.

When the specified function (handler) returns “normally” (i.e., without calling exit or using something like longjmp), execution resumes from the interrupted instruction.

Upon success, signal returns the function previously associated with the given signal (or SIG_DFL if no custom handler was set before). If an error occurs, signal returns SIG_ERR.

Note: A signal handler should be kept as minimal as possible, because certain functions are not safe to call within signal handlers. This is due to the asynchronous nature of signals and potential concurrency issues.

Example of using signal


#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void handlerCtrlC(int sig) {
    printf("Signal %d received (Ctrl + C).\n", sig);
    // For instance, gracefully terminate the program:
    exit(EXIT_SUCCESS);
}

int main(void) {
    // Associate the SIGINT signal (Ctrl + C) with our handler
    if (signal(SIGINT, handlerCtrlC) == SIG_ERR) {
        perror("Error calling signal");
        return EXIT_FAILURE;
    }

    printf("Waiting for Ctrl + C...\n");
    while (1) {
        // Infinite loop to illustrate signal handling
        // Additional code can be placed here
    }

    return 0;
}

4.2. The raise function


// Prototype
int raise(int signum);

The raise function sends the signal whose number is signum to the current process. This is effectively the same as the process sending itself that signal. If a handler is defined for that signal, it will be executed. Otherwise, the default (or ignored) action applies.

Example of using raise

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void handler(int sig) {
    printf("Signal %d captured (SIGTERM).\n", sig);
    // Perform specific actions or terminate
    exit(EXIT_SUCCESS);
}

int main(void) {
    // Associate SIGTERM with our handler
    if (signal(SIGTERM, handler) == SIG_ERR) {
        perror("Error calling signal");
        return EXIT_FAILURE;
    }

    printf("Sending SIGTERM to ourselves...\n");
    raise(SIGTERM);

    printf("This line won't appear if the handler terminates the program.\n");
    return 0;
}

The code below illustrates a typical use of sig_atomic_t.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

/* 
  * We use a volatile sig_atomic_t so the child’s PID
  * can be written inside a signal handler and safely
  * read in main.
  */
volatile sig_atomic_t child_pid = 0;

/* SIGCHLD handler: reaps the child and records its PID */
void sigchld_handler(int sig)
{
    int saved_errno = errno;   /* Save errno in case waitpid alters it */

    pid_t pid = waitpid(-1, NULL, 0); 
    if (pid > 0) {
        child_pid = pid;       /* Store the PID that was reaped */
    }

    errno = saved_errno;       /* Restore errno */
}

/* SIGINT handler: Could clean up or set flags for graceful shutdown */
void sigint_handler(int sig)
{
    printf("\nCaught SIGINT. Preparing to exit...\n");
    /* 
      * Optionally set a flag here to notify main 
      * that it should exit or clean up.
      */
    /* For this demo, we'll just exit immediately: */
    exit(EXIT_SUCCESS);
}

int main(void)
{
    struct sigaction sa_chld, sa_int;

    /* Set up SIGCHLD handler */
    sa_chld.sa_handler = sigchld_handler;
    sa_chld.sa_flags   = 0;   /* or SA_RESTART, if desired */
    sigemptyset(&sa_chld.sa_mask);
    if (sigaction(SIGCHLD, &sa_chld, NULL) == -1) {
        perror("sigaction SIGCHLD");
        return EXIT_FAILURE;
    }

    /* Set up SIGINT handler */
    sa_int.sa_handler = sigint_handler;
    sa_int.sa_flags   = 0;
    sigemptyset(&sa_int.sa_mask);
    if (sigaction(SIGINT, &sa_int, NULL) == -1) {
        perror("sigaction SIGINT");
        return EXIT_FAILURE;
    }

    /* Fork a child process */
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return EXIT_FAILURE;
    }
    else if (pid == 0) {
        /* Child process: do some quick work and exit */
        printf("Child: PID %d. Doing some work...\n", getpid());
        sleep(2);
        printf("Child: done. Exiting.\n");
        _exit(0);
    }

    /* Parent process: main loop */
    printf("Parent: PID %d. Waiting for child (PID %d) to exit...\n", getpid(), pid);
    while (1) {
        if (child_pid != 0) {
            printf("Parent: detected that child %d terminated.\n", child_pid);
            break;
        }
        /* Do other work or simply sleep/pause to wait for signals */
        sleep(1);
    }

    printf("Parent: Exiting.\n");
    return 0;
}

Explanation of the code:

  1. volatile sig_atomic_t child_pid = 0;
    • This global variable is used to store the PID of the child that has just exited.
    • It is declared as volatile sig_atomic_t because it’s written inside a signal handler (sigchld_handler) and read in the main loop.
  2. sigchld_handler()
    • It is registered for SIGCHLD, which is sent to the parent when a child terminates or stops.
    • Calls waitpid(-1, NULL, 0) to reap any dead child (here, we assume only one child for simplicity).
    • Assigns the retrieved PID to child_pid.
    • Restores errno to avoid corrupting any ongoing system calls or library functions in the main process.
  3. sigint_handler()
    • It is registered for SIGINT (Ctrl+C).
    • In this minimal example, it just prints a message and exits. In a more robust program, it could set a flag, free resources, or perform other cleanup before exiting.
  4. Main process flow
    • After installing the signal handlers with sigaction(), the program forks a child.
    • The child does some simulated “work” for 2 seconds, then exits.
    • Meanwhile, the parent checks child_pid in a loop. As soon as the SIGCHLD handler sets child_pid to the reaped child’s PID, the parent knows the child has exited and can proceed (in this case, just print a message and exit).

5. Additional remarks

  • Signals can occur at any time, interrupting the normal flow of execution. It is crucial not to call functions from within a signal handler that are not “async-signal-safe,” as this may lead to inconsistent states.
  • Some signals (like SIGKILL and SIGSTOP) cannot be caught, ignored, or reassigned. They forcibly stop or pause a process without the program’s ability to handle them.

feature-top
Readers’ comment
feature-top
Log in to add a comment
🔐 Access