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

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

在他的网页关于自管技巧,丹·伯恩斯坦(Dan Bernstein)用 和 信号解释了竞争条件select(),提供了一种解决方法,并得出结论:

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

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

答案1

该问题在您的源代码中进行了描述,select()应该被诸如 之类的信号中断SIGCHLD,但在某些情况下它效果不佳。因此,解决方法是将信号写入管道,然后由 监视select()。监视文件描述符就是select()为了解决这个问题。

该解决方法本质上是将信号事件转换为文件描述符事件。如果fork()首先返回一个 fd,则不需要解决方法,因为该 fd 可能可以直接与 一起使用select()

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


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

答案2

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

答案3

这只是对“如果 Unix 的设计与现在不同,那就太好了”这样的思考。

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

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

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

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

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

答案4

要点是,有许多程序运行基于监视文件描述符的事件循环样式模型选择(2)/民意调查(2)/电子轮询(7)。但历史上有多种事件没有使用文件描述符进行通知——其中包括子进程的进程状态转换(例如,终止)。需要单独处理这些其他事件(包括计时器到期、信号和同步事件(例如信号量更改)),这使得事件循环模型的编程变得复杂。

在过去的几年里,Linux 的开发一直在解决这个问题,所以现在我们有了信号fd(2)(使信号从文件描述符中可读),事件fd(2)(句柄是文件描述符的同步原语),以及定时器fd_创建(2)(创建通过文件描述符通知的计时器),所有这些都会生成可以馈送到的文件描述符选择(2)/民意调查(2)/电子轮询(7)

最后,最新版本的 Linux 添加了进程句柄的概念作为文件描述符。CLONE_PIDFD的旗帜克隆3()可用于创建一个子进程,为其返回文件描述符作为句柄。文件描述符同样可以输入选择(2)/民意调查(2)/电子轮询(7)如果子进程终止,则指示为可读。

相关内容