Class 28 of 37 • System Programming (C) \t\t\t\t

Linux Signal Handling in C Programming

In Linux and UNIX systems, Signals are asynchronous notifications sent by the Linux kernel to a process to inform it that a specific system or hardware event has occurred. Signals interrupt the normal linear execution flow of a process, forcing it to immediately address the event. Understanding signals is critical for developing robust system daemons, network servers, and fault-tolerant C applications.

Linux Signal Lifecycle and Handling Architecture

Figure 1: The 3 stages of a Linux signal: Generation, Kernel Pending Queue, and Process Disposition.

Standard Linux Signals Reference Table

Linux defines over 30 standard POSIX signals in the <signal.h> header file. The table below lists the most vital signals every systems programmer must know:

Signal Name

Number

Trigger / Origin

Default Action

SIGHUP

1

Hangup detected on controlling terminal or death of controlling process. Daemons use it to reload config files.

Terminate

SIGINT

2

Terminal interrupt generated when user presses Ctrl + C.

Terminate

SIGQUIT

3

Terminal quit generated when user presses Ctrl + \.

Core Dump

SIGFPE

8

Erroneous arithmetic operation (e.g., integer division by zero).

Core Dump

SIGKILL

9

Unconditional process kill. CANNOT BE CAUGHT OR IGNORED!

Forced Kill

SIGSEGV

11

Invalid memory reference / Segmentation fault (dereferencing NULL pointer).

Core Dump

SIGPIPE

13

Broken pipe: write to pipe or socket with no readers reading from it.

Terminate

SIGALRM

14

Timer signal generated by alarm() or setitimer() system calls.

Terminate

SIGTERM

15

Standard graceful termination request sent by kill <pid> or system shutdown.

Terminate

SIGCHLD

17

Sent to parent process when a child process terminates or stops.

Ignore

SIGSTOP

19

Stop process execution. CANNOT BE CAUGHT OR IGNORED!

Pause Process

Signal Dispositions (Handling Options)

When a signal arrives at a process, it can respond in one of three ways:

  • Default Action (SIG_DFL): The process executes the kernel's default behavior (usually terminating the process or creating a memory core dump).
  • Ignore Signal (SIG_IGN): The process discards the signal completely. The program continues execution unaffected. (Note: SIGKILL and SIGSTOP cannot be ignored!).
  • Catch the Signal (Custom Handler): The process registers a user-defined C function pointer. When the signal fires, the kernel suspends normal execution and runs the handler.

Basic Signal Handling with signal()

The traditional C library function for registering a signal handler has the prototype:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

Compilable C Example: Catching SIGINT (Ctrl+C) and SIGALRM

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

// Custom signal handler function
void handle_sigint(int sig) {
    printf("\n[CAUGHT SIGNAL %d] Ctrl+C intercepted! Cleaning up resources...\n", sig);
}

void handle_alarm(int sig) {
    printf("[ALARM TIMER] 3 seconds elapsed! Waking up background worker...\n");
}

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

    // 2. Register handler for SIGALRM
    signal(SIGALRM, handle_alarm);

    // Schedule an alarm signal in 3 seconds
    alarm(3);

    printf("Process PID: %d running. Press Ctrl+C or wait 3s for alarm.\n", getpid());

    // Loop indefinitely to demonstrate signal interruptions
    for (int i = 1; i <= 6; i++) {
        printf("Working step %d/6...\n", i);
        sleep(1);
    }

    printf("Program finished gracefully.\n");
    return 0;
}

Robust Signal Handling with sigaction()

In professional production software and modern Linux systems, sigaction() is strongly preferred over signal(). The historical signal() function resets the signal handler back to SIG_DFL on older systems after one execution, causing potential race conditions. sigaction() avoids this by providing persistent handlers and explicit signal masking.

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

volatile sig_atomic_t keep_running = 1;

void graceful_shutdown(int sig) {
    // sig_atomic_t ensures atomic write without lock corruption
    keep_running = 0;
}

int main() {
    struct sigaction sa;
    
    // Clear structure
    sa.sa_handler = graceful_shutdown;
    sigemptyset(&sa.sa_mask); // Block no extra signals during handler
    sa.sa_flags = 0;           // Standard options

    // Register sigaction for SIGINT (Ctrl+C) and SIGTERM (kill command)
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("Error registering SIGINT");
        return 1;
    }
    if (sigaction(SIGTERM, &sa, NULL) == -1) {
        perror("Error registering SIGTERM");
        return 1;
    }

    printf("Server listening (PID: %d). Send SIGTERM or press Ctrl+C to stop.\n", getpid());

    while (keep_running) {
        printf("Server processing requests...\n");
        sleep(2);
    }

    printf("\nGraceful shutdown sequence complete. Exiting safely.\n");
    return 0;
}

Async-Signal-Safe Functions

When writing signal handlers, you must be careful: not all C library functions are safe to call inside a signal handler! Standard I/O functions like printf(), malloc(), and free() use internal lock mutexes. If a signal arrives while printf is already executing, calling printf again inside the handler will cause a permanent application deadlock!

  • Safe to call: write(), _exit(), read(), close(), kill(), signal().
  • Unsafe: printf(), sprintf(), malloc(), free(), exit().

Lab Practice: Inter-Process Signaling with kill() and Parent-Child Communication

Problem: Write a C program where a child process waits for a custom user signal (SIGUSR1) from its parent process before proceeding with execution.

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

volatile sig_atomic_t signal_received = 0;

void user_signal_handler(int sig) {
    signal_received = 1;
}

int main() {
    pid_t pid;

    // Register custom signal handler
    signal(SIGUSR1, user_signal_handler);

    pid = fork();

    if (pid < 0) {
        perror("Fork failed");
        return 1;
    }

    if (pid == 0) {
        // --- CHILD PROCESS ---
        printf("[CHILD PID: %d] Waiting for authorization signal from parent...\n", getpid());
        
        while (!signal_received) {
            pause(); // Suspend process until any signal is received
        }

        printf("[CHILD] Received SIGUSR1 authorization! Commencing critical task.\n");
        exit(0);
    } else {
        // --- PARENT PROCESS ---
        printf("[PARENT PID: %d] Preparing data (waiting 2 seconds)...\n", getpid());
        sleep(2);

        printf("[PARENT] Sending SIGUSR1 to Child PID %d...\n", pid);
        kill(pid, SIGUSR1); // Send signal to child

        wait(NULL); // Wait for child to finish
        printf("[PARENT] Child finished. Parent exiting.\n");
    }

    return 0;
}

Frequently Asked Questions (FAQs)

SIGINT (2) is generated interactively from terminal by Ctrl+C. SIGTERM (15) is the default software termination request sent by kill <pid>, allowing the program to flush files and release ports. SIGKILL (9) directly kills the process via the kernel without allowing the program to catch, intercept, or clean up.
SIGKILL (signal 9) and SIGSTOP (signal 19) cannot be caught, blocked, or ignored. This guarantees that system administrators can always stop or terminate rogue processes.
sig_atomic_t is an integer data type guaranteed to be accessed atomically (as a single instruction) by the CPU architecture, ensuring that reads and writes cannot be interrupted midway by incoming signals.
printf() is non-reentrant because it locks an internal standard output mutex. If interrupted by a signal while executing printf(), attempting to run printf() inside the handler results in deadlock. Use the low-level write() system call instead.

Next Step in Your Linux Learning Path:

Implement multi-threaded concurrency and mutex locking in Class 29: POSIX Threads (pthreads).

Continue to Class 29: POSIX Threads (pthreads) → Previous: ← IPC | Full Syllabus

Share Share on Facebook Share on Twitter Share on LinkedIn Pin on Pinterest Share on Stumbleupon Share on Tumblr Share on Reddit Share on Diggit

You may also like this!