当我启动终端模拟器(例如 qterminal)时,它会启动默认 shell(例如 bash)。当我退出终端时(例如,通过单击 x 按钮或终止终端),shell 也会退出。外壳没有留下任何进程。我想知道这个机制是如何实现的。
起初,我怀疑终端向 shell 发送了一些信号。所以我在 shell 中捕获了 SIGQUIT、SIGINT、SIGTERM、SIGHUP,但没有捕获任何信号。除了信号我不知道。
它可能取决于终端和外壳,也可能取决于操作系统。请告诉我有关这种情况的任何信息。
答案1
通常终端会向进程发送挂断信号(SIGHUP)。此外,当终端关闭 pty 的“主”端时,附加到它的某些进程也会自动从内核接收 SIGHUP — 无论终端是否发送 SIGHUP。
虽然来自内核的信号不会发送到所有以 pty 作为控制终端的进程 - 我没有具体检查,但我认为它专门传递到“前台 pgroup”,基本上与 SIGINT/ 相同来自 Ctrl+C/Ctrl+\ 的 SIGQUIT。
Shell 本身(例如 Bash)已经有一个 SIGHUP 处理程序,它向所有其他 shell 发送额外的 SIGHUP后台工作由 shell 管理(忽略那些被disown
ed 的),例如,sleep 1h &
当 Bash 由于自己收到 SIGHUP 而即将退出时, a 将被 Bash 发出 SIGHUP。
例如,当干净地关闭终端时:
$ 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)
[...]
close(ptmx_fd)
当使用调试器从终端进程发出时:
$ 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)
最后,当 pty 关闭时,shell(或其他前台程序)在尝试从中读取时将收到 EOF(即read()
返回 0),它会像按 Ctrl+D 一样处理它 - 通常也会这样做导致程序退出,当然它可以忽略 EOF (我见过一个特定的程序在这种情况下进入无限循环,当它决定忽略 SIGHUP和EOF)。
shell 很可能在 EOF 时退出前它到达您的自定义 SIGHUP 处理程序。例如,我可以在 GNOME 终端下的 Bash 中重现您的结果(不trap ... SIGHUP
被调用),然而,如果我另外设置IGNOREEOF=1
告诉 Bash 不要在 EOF 时退出,那么确实会调用自定义 SIGHUP 陷阱。
自定义陷阱也发生了覆盖Bash 的默认“SIGHUP 时退出”行为。所以当我告诉 Bash 忽略 EOF 时和trap SIGHUP,它实际上一直在尝试向不存在的 tty 打印提示——“strace”输出显示它调用了我常用的所有 PROMPT_COMMAND 挂钩,尝试将提示写入 stdout,获取 EIO,然后尝试将错误消息写入 stderr ,从 EIO 中获取那,只有在之后它才决定受够了并退出。