Handling SIGHUP by the Shell in Linux Explained
Handling SIGHUP by the Shell in Linux Explained
· The Linux Programming Interface · EmbeddedPathashala
Key Terms in This Section
What Does the Shell Do When It Receives SIGHUP?
The login shell is typically the controlling process for the terminal. Most shells (bash, ksh) install a SIGHUP handler. When SIGHUP arrives (e.g., terminal window closed), the handler does the following before terminating:
- Sends SIGHUP to each process group the shell created (both foreground and background jobs).
- On some shells (bash, ksh), also sends SIGHUP to stopped background jobs if the shell exits normally (logout or Ctrl+D).
Key point: The shell only sends SIGHUP to process groups it created. If a child process created its own process group (moved to a new PGID), the shell does not send SIGHUP to that group — even though it is in the same session.
catch_SIGHUP (which creates a child that moves to a new PGID) and close the terminal. The parent and same-group child receive SIGHUP; the different-group child does NOT — even though they share the same session.The catch_SIGHUP Experiment Explained
The book’s catch_SIGHUP program creates a parent and child. Optionally, the child moves to its own process group. Here is what happens when the terminal is closed:
| Scenario | What happens |
|---|---|
| Parent and child in same PGID (shell’s job) | Both receive SIGHUP from the shell |
| Child moved to a different PGID | Parent receives SIGHUP; child does NOT (shell didn’t create that group) |
nohup and disown — Protecting Processes from SIGHUP
nohup(1) command: Runs a command with SIGHUP set to SIG_IGN. The process survives terminal close and shell exit. Output is redirected to nohup.out if stdout is a terminal.
disown (bash built-in): Removes a job from bash’s job table. Bash does not send SIGHUP to disowned jobs when the shell exits — the job is simply forgotten by the shell.
nohup myprogram & — immune to SIGHUP
disown %1 — remove job 1 from bash’s table
Code Example 1 — catch_SIGHUP: Same Group vs Different Group
/* catch_sighup.c
* Reproduces the book's catch_SIGHUP program.
* Creates a parent + child.
* If a command-line argument is given, child moves to a new PGID.
* Run in background, close terminal, observe who receives SIGHUP.
*
* Compile: gcc -o catch_sighup catch_sighup.c
* Run (same group): ./catch_sighup > samegroup.log 2>&1 &
* Run (diff group): ./catch_sighup x > diffgroup.log 2>&1 &
* Then close terminal and: cat samegroup.log / cat diffgroup.log
*/
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
static void handler(int sig)
{
/* Empty — just catches the signal so process doesn't terminate */
(void)sig;
}
int main(int argc, char *argv[])
{
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = handler;
/* Install SIGHUP handler */
if (sigaction(SIGHUP, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
pid_t childPid = fork();
if (childPid == -1) { perror("fork"); return 1; }
if (childPid == 0) {
/* --- CHILD --- */
/* If argument given, move to own process group */
if (argc > 1) {
if (setpgid(0, 0) == -1) {
perror("setpgid");
return 1;
}
}
}
/* Parent and child both reach here */
printf("PID=%ld; PPID=%ld; PGID=%ld; SID=%ld\n",
(long)getpid(),
(long)getppid(),
(long)getpgrp(),
(long)getsid(0));
fflush(stdout);
alarm(60); /* Ensure termination if not signalled */
for (;;) {
pause(); /* Wait for signals */
printf("%ld: caught SIGHUP\n", (long)getpid());
fflush(stdout);
}
}
/*
* samegroup.log (no argument):
* PID=5612; PPID=5611; PGID=5611; SID=5533 ← child (same group)
* PID=5611; PPID=5533; PGID=5611; SID=5533 ← parent
* 5611: caught SIGHUP ← parent got it
* 5612: caught SIGHUP ← child got it too
*
* diffgroup.log (with argument):
* PID=5614; PPID=5613; PGID=5614; SID=5533 ← child (diff PGID!)
* PID=5613; PPID=5533; PGID=5613; SID=5533 ← parent
* 5613: caught SIGHUP ← parent got it
* (child did NOT get it — shell never created PGID 5614)
*/
Code Example 2 — Programmatic nohup: SIG_IGN before exec
/* my_nohup.c
* Simplified implementation of the nohup(1) command.
* Sets SIGHUP to SIG_IGN, then execs the given program.
* Compile: gcc -o my_nohup my_nohup.c
* Usage: ./my_nohup sleep 100
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <command> [args...]\n", argv[0]);
return 1;
}
/* Step 1: Ignore SIGHUP — this disposition is preserved across exec() */
if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
perror("signal");
return 1;
}
/* Step 2: If stdout is a terminal, redirect to nohup.out */
if (isatty(STDOUT_FILENO)) {
fprintf(stderr, "Appending output to nohup.out\n");
int fd = open("nohup.out",
O_WRONLY | O_CREAT | O_APPEND, 0600);
if (fd == -1) {
perror("open nohup.out");
return 1;
}
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("dup2");
return 1;
}
close(fd);
}
/* Step 3: exec the requested command
* SIG_IGN for SIGHUP is preserved across execv() per POSIX */
execvp(argv[1], &argv[1]);
perror("execvp"); /* Only reached on error */
return 127;
}
/* Test:
* ./my_nohup bash -c 'for i in 1 2 3 4 5; do sleep 2; echo tick $i; done'
* (close terminal)
* cat nohup.out → still prints tick 1 through tick 5
*/
