Linux Process Signals: Mechanisms and Management

Signal Concepts and Generation

Signals serve as a mechanism for the operating system to notify processes of asynchronous events. In a Linux environment, signals allow the OS or users to interrupt a running process to handle specific tasks, such as termination, suspension, or custom logic. Each signal has a unique name starting with 'SIG' (e.g., SIGINT, SIGKILL) and a corresponding integer value. The range 1-31 typically represents standard signals with distinct purposes.

Signals can be generated through various inputs. A common example is the terminal interrupt key combination Ctrl+C, which sends signal number 2 (SIGINT) to the foreground process group. Similarly, Ctrl+\ sends signal 3 (SIGQUIT). Processes can also send signals programmatically using system calls.

Handling Signals

When a signal is delivered, a process can respond in one of three ways:

  • Default Action: The kernel performs the preset behavior (e.g., terminating the process).
  • Ignore: The signal is discarded, and no action is taken.
  • Catch: The process executes a user-defined function upon receiving the signal.

The signal function allows a process to register a custom handler. The following example demonstrates intercepting SIGINT to prevent immediate termination:

#include <iostream>
#include <csignal>
#include 

void signalHandler(int signum) {
    std::cout << "Received signal: " << signum << std::endl;
    std::cout << "Performing cleanup..." << std::endl;
    _exit(0);
}

int main() {
    // Register handler for SIGINT (Ctrl+C)
    signal(SIGINT, signalHandler);

    while (true) {
        std::cout << "Process running with PID: " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

Besides keyboard inputs, signals can be generated via the kill command or API. The kill system call sends a signal to a specific process ID. Additionally, raise sends a signal to the current process, and abort sends SIGABRT to force termination.

Hardware and Software Exceptions

Hardware anomalies often trigger signals. For instance, dividing an integer by zero causes the CPU to raise a hardware exception. The OS kernel catches this and sends SIGFPE (signal 8) to the offending process. Similarly, invalid memory access results in SIGSEGV (signal 11).

#include <iostream>
#include <csignal>
#include <cstdlib>

void floatingPointHandler(int signum) {
    std::cout << "Arithmetic exception detected (Signal " << signum << ")" << std::endl;
    exit(1);
}

int main() {
    signal(SIGFPE, floatingPointHandler);
    int value = 10;
    int result = value / 0; // Triggers SIGFPE
    return 0;
}

Software conditions also generate signals. For example, writing to a pipe with no reader triggers SIGPIPE (signal 13). The alarm function generates SIGALRM (signal 14) after a specified number of seconds.

Signal Management and Blocking

Signal States

The kernel maintains three distinct data structures for signal handling within a process's Process Control Block (PCB):

  • Pending Set: A bitmap indicating signals that have been sent but not yet delivered.
  • Block Set: A bitmap of signals the process has requested to block temporarily.
  • Handler Table: An array of function pointers defining the action for each signal.

If a signal is blocked, it remains in the pending set until unblocked. A signal cannot be delivered if it is blocked, but it is not lost.

Signal Set Operations

Linux provides the sigset_t data type and a suite of functions to manipulate signal sets. Functions like sigemptyset, sigfillset, and sigaddset allow modification of these sets. The sigprocmask function is used to read or change the current block set.

#include <iostream>
#include <csignal>
#include 

void showPendingSignals() {
    sigset_t pendingSet;
    sigpending(&pendingSet);

    std::cout << "Pending signals: ";
    for (int i = 1; i <= 31; ++i) {
        if (sigismember(&pendingSet, i)) {
            std::cout << "1";
        } else {
            std::cout << "0";
        }
    }
    std::cout << std::endl;
}

int main() {
    sigset_t blockSet, originalSet;

    // Initialize and add SIGINT(2) to the block set
    sigemptyset(&blockSet);
    sigaddset(&blockSet, SIGINT);

    // Block SIGINT
    sigprocmask(SIG_BLOCK, &blockSet, &originalSet);

    std::cout << "SIGINT blocked for 5 seconds. Try Ctrl+C." << std::endl;

    int count = 0;
    while (count < 5) {
        showPendingSignals();
        sleep(1);
        count++;
    }

    // Restore original set (unblock)
    sigprocmask(SIG_SETMASK, &originalSet, nullptr);
    std::cout << "SIGINT unblocked." << std::endl;

    return 0;
}

Advanced Handling with sigaction

The sigaction function provides more robust control than signal. It allows specifying a handler and managing additional behavior, such as blocking specific signals during the handler's execution. The struct sigaction contains the handler, a mask of signals to block during execution, and flags.

#include <iostream>
#include <csignal>
#include <cstring>
#include 

void customHandler(int signum) {
    std::cout << "Handled signal " << signum << " via sigaction." << std::endl;
}

int main() {
    struct sigaction act, oldAct;

    memset(&act, 0, sizeof(act));
    act.sa_handler = customHandler;
    sigemptyset(&act.sa_mask);
    
    // Block SIGQUIT while handling SIGINT
    sigaddset(&act.sa_mask, SIGQUIT);
    act.sa_flags = 0;

    sigaction(SIGINT, &act, &oldAct);

    std::cout << "Process waiting for signals." << std::endl;
    while(true) {
        pause();
    }
    return 0;
}

When using sigaction, the kernel automatically clears the pending bit for the signal being handled before invoking the handler. If a signal is blocked using sa_mask, multiple instances of that signal arriving during the handler execution are queued in the pending set as a single occurrence for standard signals.

Tags: Linux Operating System signals C++ System Programming

Posted on Sun, 20 Sep 2026 16:50:25 +0000 by reapfyre