The SIGHUP Signal

 

34.6 — The SIGHUP Signal
Chapter 34 · The Linux Programming Interface · EmbeddedPathashala
Terminal disconnects, chain reactions, and daemon reinitialisation
Key Terms in This Section

SIGHUP Terminal disconnect Modem hangup Pseudoterminal Chain reaction Controlling process EIO on read() SIGCONT Daemon reinitialisation nohup

What Triggers SIGHUP?

SIGHUP (signal number 1) is the “Hangup” signal. It was originally designed for modem-based terminals — when the phone line dropped (hung up), the terminal driver notified the process.

Today, SIGHUP is triggered in two situations:

  1. Modem/serial line disconnect: The terminal driver detects a loss of signal on a modem or terminal line.
  2. Terminal window closed: When you close a terminal window (xterm, gnome-terminal, etc.), the last file descriptor for the master side of the pseudoterminal is closed. This causes a disconnect event.

In both cases, the kernel sends SIGHUP to the controlling process (usually the login shell).

What Happens When the Controlling Process Receives SIGHUP?

The default action of SIGHUP is to terminate the process. If the controlling process (shell) terminates, a chain reaction begins.

The chain reaction happens in two ways:

Chain Reaction 1 — Shell forwards SIGHUP to its jobs:
Most shells handle SIGHUP and, before terminating, send SIGHUP to each process group they created (both foreground and background). This kills those jobs by default, or notifies them if they have a SIGHUP handler.
Chain Reaction 2 — Kernel sends SIGHUP to foreground group:
When the controlling process terminates (for any reason, not just SIGHUP), the kernel:
(a) Disassociates all session processes from the controlling terminal
(b) Frees the terminal for another session
(c) Sends SIGHUP (+ SIGCONT on Linux) to all members of the terminal’s foreground process group

Terminal Window Closed
SIGHUP sent to Controlling Process (bash)
Shell forwards SIGHUP
to its background jobs
Kernel sends SIGHUP
to foreground group

SIGHUP After the Controlling Process Terminates — Read() Behaviour

If the controlling process handles or ignores SIGHUP (does not terminate), then any subsequent read() from the terminal by other processes will return end-of-file.

Special Use: Daemon Reinitialisation

Important convention: Because a daemon has no controlling terminal (and thus cannot receive SIGHUP from the kernel normally), system administrators use SIGHUP as a conventional signal to tell a daemon to reinitialise itself or reread its configuration file.

Example: kill -HUP $(cat /var/run/nginx.pid) — this tells nginx to reload its config without stopping.

Code Example 1 — Catching and Logging SIGHUP

/* sighup_demo.c
 * Simple program that catches SIGHUP and logs it.
 * Run in background, then close the terminal to observe SIGHUP.
 * Compile: gcc -o sighup_demo sighup_demo.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>

static volatile int sighup_count = 0;

static void sighup_handler(int sig)
{
    /* Signal-safe: only increment a counter here.
     * Do real work in the main loop. */
    sighup_count++;
}

int main(void)
{
    /* Install SIGHUP handler */
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sa.sa_handler = sighup_handler;
    if (sigaction(SIGHUP, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    printf("PID=%ld  PGID=%ld  SID=%ld\n",
           (long)getpid(), (long)getpgrp(), (long)getsid(0));
    printf("Waiting for SIGHUP (close terminal or: kill -HUP %ld)\n",
           (long)getpid());
    fflush(stdout);

    int prev_count = 0;
    while (1) {
        sleep(1);
        if (sighup_count != prev_count) {
            /* Real work done here, not in handler */
            printf("Received SIGHUP (total count: %d) — reinitialising...\n",
                   sighup_count);
            fflush(stdout);
            /*
             * In a real daemon, this is where you would:
             * - reread config files
             * - reopen log files
             * - reset state
             */
            prev_count = sighup_count;
        }
    }
    return 0;
}
/* Send SIGHUP manually: kill -HUP <PID>
 * Output: Received SIGHUP (total count: 1) — reinitialising...
 * This is exactly how nginx, sshd, etc. reload their configs.
 */

Code Example 2 — nohup() Effect: Ignoring SIGHUP

/* nohup_demo.c
 * Demonstrates the effect of ignoring SIGHUP (like nohup(1) does).
 * Compile: gcc -o nohup_demo nohup_demo.c
 * Normal run:  ./nohup_demo        ← will die on terminal close
 * Nohup run:   nohup ./nohup_demo &  ← survives terminal close
 *
 * This code manually does what nohup(1) does for you.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

int main(int argc, char *argv[])
{
    /* If --immune flag is given, ignore SIGHUP (like nohup) */
    if (argc > 1 && strcmp(argv[1], "--immune") == 0) {
        if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
            perror("signal");
            return 1;
        }
        printf("SIGHUP is IGNORED — this process is immune to terminal close\n");
    } else {
        printf("SIGHUP has DEFAULT action — this process will die on terminal close\n");
    }

    printf("PID=%ld running... close terminal or: kill -HUP %ld\n",
           (long)getpid(), (long)getpid());
    fflush(stdout);

    /* Stay alive and periodically write to a log file */
    FILE *log = fopen("/tmp/nohup_demo.log", "a");
    if (!log) log = stderr;

    for (int i = 0; i < 60; i++) {
        sleep(1);
        fprintf(log, "tick %d (PID=%ld)\n", i, (long)getpid());
        fflush(log);
    }

    if (log != stderr) fclose(log);
    return 0;
}
/* Test:
 *   ./nohup_demo --immune &
 *   close terminal window
 *   check /tmp/nohup_demo.log — process kept running!
 */

Interview Questions — Section 34.6

Q1. What are the two situations that cause the kernel to send SIGHUP to the controlling process?
First, when a disconnect is detected by the terminal driver — for example, a modem hangup or loss of signal on a serial line. Second, when a terminal window is closed — because closing the window causes the last file descriptor for the master side of the pseudoterminal to be closed, which the driver treats as a disconnect.
Q2. What is the “chain reaction” caused by SIGHUP?
There are two parts to the chain reaction. First, the shell (the controlling process) typically handles SIGHUP and forwards it to all process groups it created before dying. Second, when the controlling process terminates, the kernel sends SIGHUP to all members of the foreground process group. This can cascade to kill many processes that were running in that terminal session.
Q3. Why do daemons use SIGHUP as a reinitialisation signal?
A daemon has no controlling terminal, so it will never receive SIGHUP from the kernel due to a terminal disconnect. System administrators exploit this by sending SIGHUP manually (kill -HUP) as a conventional signal meaning “reload your configuration”. The daemon installs a SIGHUP handler that rereads config files or reopens log files without stopping the service.
Q4. What does nohup(1) do to protect a process from SIGHUP?
nohup sets the disposition of SIGHUP to SIG_IGN (ignore) before executing the command. With SIGHUP ignored, the process survives terminal closes and shell exits. The bash built-in command disown serves a similar purpose by removing a job from the shell’s job table so the shell does not send it SIGHUP when the shell exits.
Q5. What happens to read() calls on the terminal after the controlling process terminates?
If the controlling process handles or ignores SIGHUP (so it does not terminate), further read() calls from the terminal by other processes return end-of-file. SUSv3 notes that in some cases read() may fail with EIO instead, so portable programs should handle both possibilities.

Leave a Reply

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