I'll recreate your experiment:
# First shell
$ echo $$
41903
# Start the second shell
$ bash
$ echo $$ $PPID
41934 41903
# Start the third shell
$ bash
$ echo $$ $PPID
41938 41934
Now, in a second terminal, I'll use strace
to monitor the system calls executed and signals handled by the third shell. Note that the shell is blocked waiting for input:
$ strace -p 41938
strace: Process 41938 attached
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
In a third terminal, I'll kill the second shell (the parent of the one I'm tracing):
$ kill -9 41934
At this time, I observe nothing in the strace
output -- the third shell is still running.
Now, if I tried to type something in first terminal, I see the following in the strace
output:
read(0, 0x7ffd6879ebbf, 1) = -1 EIO (Input/output error)
...
write(2, "exit\n", 5) = 5
...
setpgid(0, 41938) = 0
exit_group(0) = ?
The third shell failed to read from standard input (file descriptor 0 -- the first parameter to read
in this case) and terminated itself. Killing the parent process seems to have changed the state of the terminal device in some way that caused the read()
in the child to fail.
Let's repeat the experiment, but this time trace the first shell -- the parent of the process that I'll kill.
$ echo $$
48134
$ bash
$ echo $$ $PPID
48163 48134
$ bash
$ echo $$ $PPID
48169 48163
In a separate terminal, trace the first shell:
$ strace -p 48134
strace: Process 48134 attached
wait4(-1,
In another terminal, kill the second shell:
$ kill -9 48163
What did the original shell do in response to its child's death?
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGKILL}], WSTOPPED|WCONTINUED, NULL) = 48163
rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [CHLD], 8) = 0
ioctl(255, TIOCSPGRP, [48134]) = 0
Notice the call to ioctl()
with the TIOCSPGRP
parameter. When the first shell noticed that its child terminated, it changed the foreground process group ID of the terminal back to itself (note that fd 255 is also an open file descriptor to the terminal device). That's what caused the read()
in the third shell to fail -- it tried to read from the device, but was not in the foreground process group.