$ tar czf archive.tgz big_directory/ | pv | gpg --encrypt > archive.tgz.gpg
tar czf archive.tgz big_directory/ | pv | gpg --encrypt > archive.tgz.gpg
tar czf archive.tgz big_directory/ | pv | gpg --encrypt > archive.tgz.gpg
Shell (PGID=1000) │ ├─ fork() → tar (PGID=1001, also leader since tar's PID=1001) ├─ fork() → pv (PGID=1001, setpgid'd to join tar's group) └─ fork() → gpg (PGID=1001, setpgid'd to join tar's group)
Shell (PGID=1000) │ ├─ fork() → tar (PGID=1001, also leader since tar's PID=1001) ├─ fork() → pv (PGID=1001, setpgid'd to join tar's group) └─ fork() → gpg (PGID=1001, setpgid'd to join tar's group)
Shell (PGID=1000) │ ├─ fork() → tar (PGID=1001, also leader since tar's PID=1001) ├─ fork() → pv (PGID=1001, setpgid'd to join tar's group) └─ fork() → gpg (PGID=1001, setpgid'd to join tar's group)
Session (SID=999, controlling terminal: /dev/pts/3) │ ├── Process Group 999 (shell — the session leader's group) │ └── bash (PID=999, PGID=999) │ ├── Process Group 1001 (foreground: tar | pv | gpg) │ ├── tar │ ├── pv │ └── gpg │ └── Process Group 1002 (background job: long-running-thing &) └── long-running-thing
Session (SID=999, controlling terminal: /dev/pts/3) │ ├── Process Group 999 (shell — the session leader's group) │ └── bash (PID=999, PGID=999) │ ├── Process Group 1001 (foreground: tar | pv | gpg) │ ├── tar │ ├── pv │ └── gpg │ └── Process Group 1002 (background job: long-running-thing &) └── long-running-thing
Session (SID=999, controlling terminal: /dev/pts/3) │ ├── Process Group 999 (shell — the session leader's group) │ └── bash (PID=999, PGID=999) │ ├── Process Group 1001 (foreground: tar | pv | gpg) │ ├── tar │ ├── pv │ └── gpg │ └── Process Group 1002 (background job: long-running-thing &) └── long-running-thing
# nohup sets SIGHUP to SIG_IGN before exec, and redirects output
nohup long-running-job &
# nohup sets SIGHUP to SIG_IGN before exec, and redirects output
nohup long-running-job &
# nohup sets SIGHUP to SIG_IGN before exec, and redirects output
nohup long-running-job &
// Parent forks, then exits — child is now orphaned
// (child is not a process group leader, so setsid() will work)
if (fork() > 0) exit(0); // Child calls setsid() — new session, no controlling terminal
setsid(); // Fork again so we're NOT the session leader
// (without O_NOCTTY, opening a TTY would give us a controlling terminal)
if (fork() > 0) exit(0); // Now we're a non-session-leader process in a session with no
// controlling terminal. SIGHUP cannot reach us through the terminal.
// We are truly detached.
// Parent forks, then exits — child is now orphaned
// (child is not a process group leader, so setsid() will work)
if (fork() > 0) exit(0); // Child calls setsid() — new session, no controlling terminal
setsid(); // Fork again so we're NOT the session leader
// (without O_NOCTTY, opening a TTY would give us a controlling terminal)
if (fork() > 0) exit(0); // Now we're a non-session-leader process in a session with no
// controlling terminal. SIGHUP cannot reach us through the terminal.
// We are truly detached.
// Parent forks, then exits — child is now orphaned
// (child is not a process group leader, so setsid() will work)
if (fork() > 0) exit(0); // Child calls setsid() — new session, no controlling terminal
setsid(); // Fork again so we're NOT the session leader
// (without O_NOCTTY, opening a TTY would give us a controlling terminal)
if (fork() > 0) exit(0); // Now we're a non-session-leader process in a session with no
// controlling terminal. SIGHUP cannot reach us through the terminal.
// We are truly detached.
Your SSH session (terminal = /dev/pts/0) │ └── tmux client ─────────Unix socket────► tmux server (setsid, no ctrl terminal) │ PTY master ─────► /dev/pts/7 │ bash (ctrl terminal = /dev/pts/7) │ your stuff, running fine
Your SSH session (terminal = /dev/pts/0) │ └── tmux client ─────────Unix socket────► tmux server (setsid, no ctrl terminal) │ PTY master ─────► /dev/pts/7 │ bash (ctrl terminal = /dev/pts/7) │ your stuff, running fine
Your SSH session (terminal = /dev/pts/0) │ └── tmux client ─────────Unix socket────► tmux server (setsid, no ctrl terminal) │ PTY master ─────► /dev/pts/7 │ bash (ctrl terminal = /dev/pts/7) │ your stuff, running fine - Job control signals. Ctrl-C, Ctrl-Z, Ctrl-\ all generate signals via the controlling terminal's line discipline.
- SIGHUP on terminal close. When the controlling terminal's last master side is closed, the kernel sends SIGHUP to the session leader and the foreground process group.
- Background I/O protection. If a background process tries to read from or write to the controlling terminal without being in the foreground group, it gets SIGTTIN or SIGTTOU. The process stops, you see "Stopped" in the shell, and you have to foreground it. Programs that reach for /dev/tty directly (rather than stdout) are explicitly targeting the controlling terminal — and the kernel enforces access control on it. - Every process belongs to a process group (PGID). Shells put pipeline members in the same group.
- Ctrl-C and Ctrl-Z deliver signals to the foreground process group, not just one process.
- Every process group belongs to a session (SID). A terminal is the controlling terminal of exactly one session.
- When a terminal closes, SIGHUP goes to the session leader and foreground process group.
- nohup ignores SIGHUP. disown removes the job from the shell's cleanup list. Both are workarounds.
- setsid() creates a new session with no controlling terminal. This is how daemons, tmux, and PTY supervisors properly detach.
- tmux survives disconnection because the server calls setsid() at startup and communicates via a Unix socket — it was never in your terminal's session. - man 2 setsid, man 2 setpgid, man 3 tcsetpgrp — the system calls that manage all of this directly
- man 7 credentials — Linux's full documentation on PID, PGID, SID and how they interact
- ps -ej — run this in a terminal with a few jobs running and watch the SID/PGID columns. Everything above becomes concrete immediately.
- The TTY Demystified — Linus Akesson's deep dive, already recommended in earlier posts. The section on job control is directly relevant here.