1

When I start a terminal emulator (for example, qterminal), it starts the default shell (for example, bash). And when I quit the terminal (for example, by clicking the x button or by killing the terminal), the shell also exits. There remainds no process of the shell. I would like to know how this mechanism is implemented.

At first, I suspected that the terminal sends some signal to the shell. So I trap SIGQUIT, SIGINT, SIGTERM, SIGHUP in the shell, but no signal was trapped. I have no idea other than signal.

It may depands on the terminal and the shell, and perhaps on the OS. Please tell me any information about this situation.

4
  • 1
  • Thank you for your comment. The linked page explains how ctrl+c terminates a command. But, as far as I understood, it does not seem to explain how the shell is forced to exit. If it does, please tell me the part of the document.
    – user356126
    Feb 22, 2022 at 7:32
  • 1
    Run the following command in the shell: strace -f -o strace.log -p $$. Then close the terminal, and check in the strace.log file which signal it received.
    – aviro
    Feb 22, 2022 at 7:35
  • @user356126 You seem to have skipped quite a few parts of this article: "Bash runs as a child process of gnome-terminal, and yes as child process of bash." "Gnome-terminal, when launching a new window or tab, makes a request to the TTY driver* in the kernel asking for a new pseudo-terminal (PTY). " ... Feb 22, 2022 at 7:36

1 Answer 1

3

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 disowned), 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.

2
  • Thank you for your answer. Your detailed explanation made me understand the exit behavior of Bash, and why I could not trap SIGHUP. Following your way, I set IGNOREEOF=1. Then, the cusdom SIGHUP trap was invoked also in my envoronment (Bash under qterminal).
    – user356126
    Feb 25, 2022 at 2:55
  • I tried to check signals using strace. In my environment, it seems that Bash sends SIGHUP to itself, and that Bash handles several signals using rt_sigaction. I could not understand why Bash behaves like that, but it made me curious.
    – user356126
    Feb 25, 2022 at 3:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .