Signals and Signal Handling

Learn how processes communicate simple notifications using signals such as SIGINT, SIGTERM, and user-defined signals, and understand how to safely register signal handlers to coordinate process behavior.

So far, we have examined IPC mechanisms that transfer data between processes: pipes stream bytes, shared memory maps regions, and message queues deliver structured messages. However, not all communication involves transferring data. Sometimes a process simply needs to notify another process that an event has occurred.

Signals provide this capability. A signal is a lightweight notification sent to a process by the operating system or by another process. Unlike pipes or message queues, signals do not carry structured data. Instead, they indicate that a particular event has occurred.

What is a signal?

A signal is an asynchronous notification delivered to a process. It interrupts the normal flow of execution and triggers a predefined action. By default, each signal has a predefined behavior. For example, SIGINT terminates a program. However, a process can override this behavior by registering a signal handler. All signal constants are declared in <signal.h>. Below is a list of commonly used signals.

  • SIGINT: Sent when the user presses "Ctrl+C" in the terminal. By default, it terminates the running program, but it can be caught and handled.

  • SIGTERM: Requests graceful termination. Unlike SIGKILL, it allows a program to intercept the signal and perform cleanup before exiting.

  • SIGKILL: Forces immediate termination of a process. This signal cannot be caught, blocked, or handled, making it a last-resort termination mechanism.

  • SIGUSR1 and SIGUSR2: User-defined signals reserved for application-specific communication. Programs can assign custom meaning to these signals for coordination or notifications.

Registering a signal handler

To handle a signal, a process registers a handler function using signal() or sigaction(). The basic syntax is as follows: