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