为什么应该将 fork() 设计为返回文件描述符?

Las*_*ssi 16 signals file-descriptors fork

在他的关于网页自管绝招,丹·伯恩斯坦解释了竞争条件select()和信号,提供一个解决办法,并得出结论认为,

当然,正确的做法是fork()返回文件描述符,而不是进程 ID。

他的意思是什么 - 是否能够select()在子进程上处理其状态更改,而不必使用信号处理程序来获取这些状态更改的通知?

ilk*_*chu 14

问题在您的来源中有所描述,select()应该被诸如 的信号中断SIGCHLD,但在某些情况下它不能很好地工作。因此,解决方法是将信号写入管道,然后由select(). 观察文件描述符是select()为了解决这个问题。

该解决方法实质上将信号事件转换为文件描述符事件。如果fork()刚开始返回一个 fd,则不需要解决方法,因为该 fd 大概可以直接与select().

所以是的,你在最后一段中的描述对我来说似乎是正确的。


fd(或某种其他类型的内核句柄)比普通进程 ID 号更好的另一个原因是,进程终止后可以重用 PID。在某些情况下,当向进程发送信号时,这可能是一个问题,可能无法确定该进程是您认为的那个进程,而不是另一个重用相同 PID 的进程。(虽然我认为这在向子进程发送信号时应该不是问题,因为父进程必须在子进程上运行wait()才能释放其 PID。)

  • 值得一提的是,现在 Linux _can_ 从 `clone` 返回一个文件描述符 (pidfd),这是 fork 在 LInux 上调用的实际系统调用。启用此功能的标志称为`CLONE_PIDFD` - 例如参见 https://lwn.net/Articles/784831/。 (7认同)
  • 正如您已经说过的,没有任何理由让父母发现自己的孩子 pid 已被重用。它完全控制了这种情况,因为它是调用 `wait()` 的人。 (2认同)

mos*_*svy 9

这只是“如果 Unix 的设计与它不同,那就太好了”的沉思。

PID 的问题在于它们存在于一个全局命名空间中,在那里它们可以被另一个进程重用,如果fork()在父进程中返回某种可以保证始终引用子进程的句柄,那就太好了,并且它可以通过继承或 unix sockets / SCM_RIGHTS[1]传递给其他进程。

另请参阅此处的讨论了解最近在 Linux 中“修复”该问题的努力,包括添加一个标志,clone()使其返回 pid-fd 而不是 PID。

但即便如此,这也不会消除对自管道黑客 [2] 或更好接口的需要,因为通知父进程有关子进程状态的信号并不是您希望在主循环中处理的唯一信号的程序。不幸的是,像epoll(7) + signalfd(2)在 Linux 或kqueue(2)BSD上的东西不是标准的——唯一的标准接口(但在较旧的系统上不支持)要差得多pselect(2)

[1] 在waitpid()系统调用返回并使用其返回值时,防止 PID 被重新循环可能可以在较新的系统上通过使用waitid(.., WNOWAIT)来实现。

[2] 我不会评论 DJ Bernstein 声称他发明了它(抱歉我的失语;-))。


Bru*_*ger 8

Bernstein 没有为这个“正确的事情”评论提供太多上下文,但我会冒险猜测:让 fork(2) 返回 PID 与 open(2)、creat(2) 等返回文件描述符不一致。Unix 系统的其余部分可以使用代表进程的文件描述符而不是 PID 来完成进程操作。系统调用signalfd(2)存在,它允许信号和文件描述符之间更好的交互,并表明表示进程的文件描述符可以工作。