POSIX Signals - SIGINT

SIGINT (Interrupt, signal number 2) is the standard POSIX signal sent by the operating system terminal driver when a user presses the interrupt key sequence—almost universally Ctrl+C.

SIGINT is designed to politely request that an interactive program stop what it is doing and terminate. Because it is catchable, blockable, and ignorable, interactive applications (such as command-line shells, text editors, and debuggers) frequently intercept SIGINT to abort an in-flight operation without closing the program itself.

How SIGINT Is Generated: The Terminal Line Discipline

When you type Ctrl+C into a terminal, the operating system kernel terminal driver intercepts the special ASCII character 0x03 (ETX / End of Text):

[ User presses Ctrl+C ]
           │
           ▼
┌────────────────────────────────────────────────────────┐
│ Terminal Subsystem (Line Discipline: n_tty)           │
│ Detects VINTR character (0x03)                         │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ Kernel queries Foreground Process Group (tcgetpgrp)    │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ Kernel broadcasts SIGINT to ALL processes in the group │
│ e.g. In "find / | grep test", both find and grep get it│
└────────────────────────────────────────────────────────┘

[!NOTE] SIGINT is targeted at the Foreground Process Group, not just a single process. In a pipeline like cat huge_file | grep target, pressing Ctrl+C sends SIGINT to both cat and grep simultaneously so the entire pipeline shuts down.

Common Handling Patterns

Pattern 1: Clean Terminal Exit (Removing Scratch Files)

Standard CLI utilities catch SIGINT to delete temporary files from /tmp before terminating:

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

volatile sig_atomic_t g_interrupted = 0;

void handle_sigint(int signum) {
    g_interrupted = 1;
}

int main() {
    struct sigaction sa = {
        .sa_handler = handle_sigint,
        .sa_flags   = 0, // Do NOT set SA_RESTART: allow slow syscalls to return EINTR
    };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);

    printf("Working... Press Ctrl+C to abort gracefully.\n");

    while (!g_interrupted) {
        // Perform work...
        sleep(1);
    }

    printf("\nInterrupt caught! Cleaning up temporary scratch files...\n");
    unlink("/tmp/my_temp_lock.pid");
    exit(130); // 128 + Signal 2 = Standard 130 exit code
}

Pattern 2: The "Double Ctrl+C" Exit Pattern

Long-running tools or interactive consoles (like Node.js REPL or GDB) use a timer to require two quick Ctrl+C presses before quitting:

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

time_t last_press = 0;

void handle_double_sigint(int signum) {
    time_t now = time(NULL);
    if (now - last_press <= 1) {
        printf("\nExiting immediately.\n");
        exit(0);
    }

    last_press = now;
    printf("\nPress Ctrl+C again within 1 second to exit.\n");
}

Pattern 3: Shells and Editors (Aborting Without Exiting)

Full-screen text editors (Vim, Nano) or shells (bash, zsh) catch SIGINT to cancel ongoing searches or clear the current input buffer, redrawing the prompt rather than terminating.

Exit Status Convention: 130

When a process terminates due to a signal, standard Unix shells report an exit code equal to 128 + Signal Number:

  • For SIGINT (signal 2), the standard shell exit code is 130 (128 + 2).
  • For SIGTERM (signal 15), the exit code is 143 (128 + 15).
  • For SIGKILL (signal 9), the exit code is 137 (128 + 9).

Frequently Asked Questions (FAQ)

What is the difference between SIGINT and SIGQUIT?

SIGINT (Signal 2, triggered by Ctrl+C) politely requests an interruption and produces no core dump, while SIGQUIT (Signal 3, triggered by Ctrl+\) terminates the process immediately and generates a core dump for debugging. Developers use Ctrl+\ when a process hangs and ignores Ctrl+C.

Why does Ctrl+C kill both commands in a piped command like cat | grep?

Because the terminal line discipline broadcasts SIGINT to the entire Foreground Process Group (PGID), which includes all processes connected in the shell pipeline. This design guarantees that multi-process pipelines terminate cleanly as a single coordinated job.

Why is the exit code 130 associated with SIGINT?

Unix shells follow the convention of setting the exit code of a process killed by a signal to 128 + signal_number. Because SIGINT is signal number 2, 128 + 2 = 130, signaling to parent scripts that the command was intentionally aborted by user keyboard interruption.

Related Topics