Usually the terminal sends a hangup (SIGHUP) to the process. Additionally, when the terminal closes its 'master' end of the pty, certain processes attached to it automatically receive a SIGHUP from the kernel as well – whether the terminal sent one or not.
Though the signal from the kernel is not sent to all processes which have the pty as their controlling terminal – I haven't checked exactly, but I think it is delivered specifically just to the "foreground pgroup", basically the same as with SIGINT/SIGQUIT from Ctrl+C/Ctrl+\.
Shells themselves such as Bash already have a handler for SIGHUP which delivers additional SIGHUPs to all background jobs managed by the shell (ignoring those which were disown
ed), e.g. a sleep 1h &
will be SIGHUP'd by Bash when the latter is about to exit due to its own received SIGHUP.
For example, when closing the terminal cleanly:
$ sudo strace -p ${pid_of_bash}
strace: Process 102874 attached
pselect6(1, [0], NULL, NULL, NULL, {sigmask=[], sigsetsize=8}) = ? ERESTARTNOHAND
--- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=87390, si_uid=1000} ---
--- SIGCONT {si_signo=SIGCONT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]}) = -1 EINTR (Interrupted system call)
--- SIGHUP {si_signo=SIGHUP, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]}) = -1 EINTR (Interrupted system call)
[...]
And when using a debugger to issue close(ptmx_fd)
from the terminal's process:
$ sudo strace -p ${pid_of_bash}
pselect6(1, [0], NULL, NULL, NULL, {sigmask=[], sigsetsize=8}) = 1 (in [0])
--- SIGHUP {si_signo=SIGHUP, si_code=SI_KERNEL} ---
--- SIGCONT {si_signo=SIGCONT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]}) = 1
read(0, "", 1) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
ioctl(2, TCXONC, TCOON) = -1 EIO (Input/output error)
write(2, "\33[?2004l\r", 9) = -1 EIO (Input/output error)
Finally, when the pty is closed, the shell (or other foreground program) will receive EOF when trying to read from it (i.e. read()
will return 0), which it'll handle just as if you pressed Ctrl+D – this too will usually cause the program to exit, though of course it could ignore the EOF (I've seen a particular program go into an infinite loop in this case, when it decided to ignore both the SIGHUP and the EOF).
It is very possible that the shell exits on EOF before it reaches your custom SIGHUP handler. For example, I can reproduce your results in Bash under GNOME Terminal (with the trap ... SIGHUP
not being called), however, if I additionally set IGNOREEOF=1
telling Bash to not quit on EOF, then the custom SIGHUP trap is indeed invoked.
A custom trap also happens to override Bash's default "exit on SIGHUP" behavior. So when I tell Bash to ignore EOFs and trap SIGHUP, it actually keeps trying to print a prompt to a nonexistent tty – the 'strace' output shows it calling all of my usual PROMPT_COMMAND hooks, trying to write the prompt to stdout, getting EIO, then trying to write error messages to stderr, getting EIO from that, and only afterwards it decides that enough is enough and exits.
strace -f -o strace.log -p $$
. Then close the terminal, and check in thestrace.log
file which signal it received.