A signal handler (also called a signal catcher) is a function you write that gets called automatically by the kernel whenever a specific signal is delivered to your process. You “install” the handler by registering it with signal() or sigaction().
When the signal arrives, the kernel temporarily diverts execution from wherever your program is running, calls your handler function, and when the handler returns, execution resumes at the exact instruction where it was interrupted.
| Step | What Happens |
|---|---|
| 1 | Main program is running normally (instruction M) |
| 2 | Signal is delivered by the kernel |
| 3 | Kernel calls signal handler on behalf of the process |
| 4 | Handler code runs |
| 5 | Handler returns โ program resumes at instruction M+1 |
A signal handler must have exactly this signature:
void handler_name(int sig)
{
/* sig contains the signal number that caused this invocation */
}
The sig parameter is useful when the same handler is registered for multiple signals โ you can check which signal triggered the call.
This is the classic example from the chapter. Instead of terminating on Ctrl+C, the program catches SIGINT and prints “Ouch!”.
/* Basic signal handler for SIGINT
Compile: gcc -o ouch ouch.c
Run: ./ouch then press Ctrl+C several times, then Ctrl+\ to quit */
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
/* Signal handler function */
static void sigHandler(int sig)
{
/* Note: printf is not safe in handlers โ used here for demo only */
printf("Ouch! Caught signal %d (SIGINT)\n", sig);
/* Returning here means main program resumes */
}
int main(void)
{
/* Install sigHandler for SIGINT */
if (signal(SIGINT, sigHandler) == SIG_ERR) {
perror("signal");
return 1;
}
int j = 0;
while (1) {
printf("Counter: %d\n", j++);
sleep(2);
}
return 0;
}
The same handler is installed for both SIGINT and SIGQUIT. The sig argument is used to differentiate them and take different actions.
/* Same handler for SIGINT and SIGQUIT
Compile: gcc -o intquit intquit.c
Run: ./intquit press Ctrl+C multiple times, then Ctrl+\ */
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
static void sigHandler(int sig)
{
static int count = 0; /* counts SIGINT arrivals */
if (sig == SIGINT) {
count++;
printf("Caught SIGINT (%d times)\n", count);
return; /* resume main program */
}
/* Must be SIGQUIT */
printf("Caught SIGQUIT โ goodbye!\n");
exit(0);
}
int main(void)
{
/* Install same handler for both signals */
signal(SIGINT, sigHandler);
signal(SIGQUIT, sigHandler);
printf("Press Ctrl+C (SIGINT) or Ctrl+\\ (SIGQUIT)\n");
/* Pause in a loop waiting for signals */
while (1)
pause(); /* sleep until a signal arrives */
return 0;
}
Real-world pattern: catch SIGTERM to do clean-up (close files, free resources) before exiting gracefully.
/* Graceful shutdown on SIGTERM
Compile: gcc -o graceful graceful.c
Run: ./graceful then from another terminal: kill <PID> */
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
static volatile int keep_running = 1;
static void sigterm_handler(int sig)
{
/* Set flag โ main loop checks this */
keep_running = 0;
/* Note: write() is async-signal-safe; printf is not */
const char msg[] = "SIGTERM received โ shutting down...\n";
write(1, msg, sizeof(msg) - 1);
}
int main(void)
{
signal(SIGTERM, sigterm_handler);
signal(SIGINT, sigterm_handler); /* also handle Ctrl+C */
printf("Running. PID=%d. Send SIGTERM or press Ctrl+C.\n", getpid());
while (keep_running) {
printf("Working...\n");
sleep(1);
}
/* Clean-up code runs here */
printf("Cleaning up temporary files...\n");
printf("Closing database connections...\n");
printf("Process exited cleanly.\n");
return 0;
}
Signal Types & Default Actions โ The complete reference table of all Linux signals
