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.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
*/
