Ch20.6 โ€“ Signal Handlers

 

Ch20.6 โ€“ Signal Handlers
Linux System Programming ยท EmbeddedPathashala
๐Ÿ“ก Topic 6 of 19
๐ŸŽฏ Intermediate
๐Ÿ’ป 3 Code Examples
โ“ Interview Q&A
๐Ÿ”‘ Key Terms
Signal Handler Handler Function Installing a Handler Caught Signal sig argument Execution Flow Async-Signal-Safe
What is a Signal Handler?

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.

๐Ÿ“ Signal Handler Execution Flow
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
๐Ÿ“ Signal Handler Signature

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.

Important: Avoid calling non-async-signal-safe functions (like printf, malloc, free) from inside a signal handler in production code. printf() is used in examples here for learning purposes only.
๐Ÿ’ป Code Example 1 โ€“ Basic SIGINT Handler (the “Ouch” Program)

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;
}
Output: Each Ctrl+C prints “Ouch!” and the loop continues. Press Ctrl+\ (SIGQUIT) to actually terminate with a core dump.
๐Ÿ’ป Code Example 2 โ€“ One Handler for Two Signals

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;
}
๐Ÿ’ป Code Example 3 โ€“ Clean Shutdown Handler

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;
}
Pattern: Use a volatile flag set by the handler; check it in the main loop. This is safer than calling complex functions inside the handler itself.
โ“ Interview Questions
Q1. What is a signal handler and how does it differ from a normal function?
A signal handler is a function that the kernel calls automatically when a signal is delivered to a process. Unlike a normal function called explicitly in code, a handler is called asynchronously at any point during program execution.
Q2. What must be the signature of a signal handler function?
void handler_name(int sig) โ€” it takes one int argument (the signal number) and returns void.
Q3. What happens to the main program when a signal handler is executing?
The main program’s execution is suspended at whatever instruction was current when the signal arrived. The kernel runs the signal handler. When the handler returns, the main program resumes from exactly where it left off.
Q4. Why should you avoid calling printf() inside a signal handler?
printf() is not async-signal-safe. It uses internal locks and buffers. If a signal arrives while printf() is already running in the main program, calling it again in the handler can cause a deadlock or heap/buffer corruption.
Q5. How do you install the same handler for multiple signals?
Call signal() once for each signal, passing the same handler function address. Inside the handler, use the sig argument to determine which signal was delivered and take different actions accordingly.
Q6. What is the purpose of the volatile keyword for a flag variable shared between the handler and main program?
volatile tells the compiler not to cache the variable in a register. Without it, the compiler might optimize the main loop to read the variable only once, and never see the update made by the signal handler.
Next Topic โ†’

Signal Types & Default Actions โ€” The complete reference table of all Linux signals

Next: Signal Types โ†’ โ† Previous

Leave a Reply

Your email address will not be published. Required fields are marked *