谁应该杀死工作?
通常,前台和后台作业在不同情况下被内核或shell发送的SIGHUP杀死。
内核什么时候发送SIGHUP?
内核将SIGHUP发送到controlling process:
- 对于真实(硬件)终端:当在终端驱动程序中检测到断开连接时,例如在调制解调器线路上挂断;
- for pseudoterminal (pty):当最后一个引用 pty 主端的描述符关闭时,例如当你关闭终端窗口时。
内核将SIGHUP发送到其他进程组:
控制进程是与控制终端建立连接的会话领导者。
通常,控制进程是您的 shell。所以,总结一下:
- 当真实或伪终端断开/关闭时,内核将
SIGHUP 发送到外壳;
- 当shell终止时内核发送
SIGHUP到前台进程组;
- 如果包含已停止的进程,内核会将
SIGHUP 发送到孤立的进程组。
请注意,如果内核不将SIGHUP 发送到后台进程组,如果它不包含停止的进程。
bash 何时发送SIGHUP?
Bash 将SIGHUP 发送到所有 个作业(前台和后台):
- 当它接收到
SIGHUP,并且它是一个交互式shell(并且作业控制支持在编译时启用);
- 当它退出时,它是一个交互式登录 shell,并设置了
huponexit 选项(并且在编译时启用了作业控制支持)。
查看更多详情here。
注意事项:
-
bash不将SIGHUP发送到使用disown从工作列表中删除的工作;
- 使用
nohup 启动的进程忽略 SIGHUP。
更多详情here.
其他的shell呢?
通常,shell 传播SIGHUP。在正常退出时生成SIGHUP 不太常见。
Telnet 或 SSH
在 telnet 或 SSH 下,连接关闭时会发生以下情况(例如,当您在 PC 上关闭 telnet 窗口时):
- 客户端被杀死;
- 服务器检测到客户端连接已关闭;
- 服务器关闭 pty 的 master 端;
- 内核检测到master pty已关闭并发送
SIGHUP到bash;
-
bash 接收SIGHUP,将SIGHUP 发送到所有作业并终止;
- 每个作业都会收到
SIGHUP 并终止。
问题
我可以使用busybox 或dropbear SSH 服务器中的bash 和telnetd 重现您的问题:有时,后台作业不会收到SIGHUP(并且不会终止)当客户端连接关闭时。
当服务器(telnetd 或 dropbear)关闭 pty 的主端时,似乎发生了竞态条件:
- 通常,
bash 接收到SIGHUP 并立即终止后台作业(如预期的那样)并终止;
- 但有时,
bash 在处理 SIGHUP 之前 在 pty 的从属端检测到 EOF。
当bash 检测到EOF 时,默认情况下它会立即终止而不发送SIGHUP。后台作业仍在运行!
解决方案
也可以配置bash在正常退出时发送SIGHUP(包括EOF):
-
确保bash 作为登录shell 启动。 huponexit works 仅用于登录外壳,AFAIK。
通过-l 选项或argv[0] 中的leading hyphen 启用登录shell。您可以将telnetd 配置为运行/bin/bash -l 或更好的/bin/login,它在登录shell 模式下调用/bin/sh。
例如:
telnetd -l /bin/登录
-
启用huponexit 选项。
例如:
shopt -s huponexit
每次在bash 会话中输入此内容,或将其添加到.bashrc 或/etc/profile。
为什么会发生比赛?
bash 仅在安全时解除阻塞信号,并在某些代码段不能被信号处理程序安全中断时阻塞它们。
这样的临界区会不时调用中断点,如果在临界区执行时收到信号,它的处理程序会延迟到下一个中断点发生或临界区已退出。
您可以从源代码中的quit.h开始挖掘。
因此,在我们的例子中,bash 有时在处于临界区时会收到SIGHUP。 SIGHUP 处理程序执行被延迟,bash 读取 EOF 并在退出临界区或调用下一个中断点之前终止。
参考
-
"Job Control" Glibc 官方手册中的部分。
- 《Linux 编程接口》一书的第 34 章“进程组、会话和作业控制”。