为什么我们需要向包含已停止进程的新孤立进程组发送 SIGHUP?

为什么我们需要向包含已停止进程的新孤立进程组发送 SIGHUP?

UNIX 环境中的高级编程书(“APUE”)说

考虑一个分叉子进程然后终止的进程。虽然这没什么异常(这种情况经常发生),但是如果当父进程终止时子进程停止(使用作业控制)会发生什么?孩子将如何延续,孩子是否知道自己已成为孤儿?

...

如果进程组不是孤立的,则位于不同进程组但在同一会话中的父进程之一有可能会重新启动非孤立进程组中已停止的进程。

...

由于当父进程终止时,该进程组将成为孤立进程,并且该进程组包含已停止的进程,因此 POSIX.1 要求向新孤立进程组中的每个进程发送挂起信号 (SIGHUP),然后发送继续信号 (SIGCONT) )。

如果担心的只是一个停止的进程在其进程组成为孤立进程后将没有机会被唤醒,那么为什么内核在其进程组成为孤立进程时不直接发送SIGCONT,并且为什么还需要发送SIGHUP呢?

答案1

事实上,令人担忧的不仅仅是停止的进程没有机会被唤醒,还在于被通知它已成为孤立进程。

你的 APUE 摘录说:

孩子有吗知道它已成为孤儿?

如果它只收到 SIGCONT,它就不会知道自己已成为孤儿。

实际上,向子进程发送 SIGHUP 正是通知该事件的选择方式。

此外,这与某些 shell(例如 Bash)在 shell 本身由于断开连接事件而收到 SIGHUP 时对其子进程所做的操作是一致的:如果您熟悉的话,它们也会通过 HUP+CONT 将此类事件传播给已停止的子进程对于这个特殊情况。另外,作为参考,请参阅 Bash 的手册页,正好位于 SIGNALS 部分。

实际上,内核的这种行为将此类 shell 的特定行为扩展到任何孤立进程组的情况。


为了进一步阐述我的答案,我首先要告诉你,历史上它曾经只是一个 SIGKILL,而不是 SIGHUP+SIGCONT。

在某些时候,这被认为是一种过于严厉的做法。事实上,通过这种方式,进程甚至没有任何机会至少“优雅地死亡”,即完成它们正在做的事情,释放资源等。

因此,SIGKILL 已更改为当前更优雅的 HUP+CONT 方法,这样进程不仅有机会自行清理,而且还可以选择继续运行(如果它们愿意)。

我不是 POSIX 规范专家,但请注意以下摘录的基本原理_exit() 系统调用的规范:

[...],如果进程的终止导致进程组成为孤立进程,[...]。组内停止的进程将永远消失。为了避免此问题,包含已停止进程的新孤立进程组将被发送 SIGHUP 信号和 SIGCONT 信号以表示他们已与会话断开连接。 SIGHUP 信号导致进程组成员终止除非他们正在捕获或忽略 SIGHUP。

也许不是一个明确的声明,但我认为非常接近。

然后,还要注意进程组的使用不是仅限于仅由 shell 应用程序实施的作业控制。你让进程拥有自己的会话,例如典型的“守护进程”进程,没有控制终端,没有控制 shell,但本身会产生几个子进程,将它们分组到进程组中,可以使用它们的进程进行处理(即发出信号)-组 ID。

事实上,尽管进程组的“shell 作业控制”特定用例是最广为人知的一种,甚至经常被规范本身称为优秀示例,但 POSIX 对“进程组”的实际定义是:

3.296 进程组

允许相关进程发出信号的进程集合。系统中的每个进程都是由进程组 ID 标识的进程组的成员。新创建的进程加入其创建者的进程组。

A通用的过程的集合。

而“作业控制”的定义是:

3.203 作业控制

允许的设施用户有选择地停止(暂停)进程的执行并在稍后继续(恢复)其执行。用户通常通过以下方式使用此功能由终端I/O驱动程序和命令解释器共同提供的交互接口

作业控制是与交互式 shell 相关的概念。

进程分组是一个更广泛的概念。

华泰

相关内容