能否采用一项流程?

26

进程A通过fork()创建了进程B。

进程A死亡,因此init收养了进程B。

一个看门狗创建了进程C。

是否有可能让C从init中接管B?


更新:

如果在A死亡之前创建了C,是否可能让C直接接管B(而不是通过init成为中间父进程)?


更新-1:

另外,我希望对为什么按照我描述的方式采用进程是一件坏事或难以实现提供任何意见。


更新-2: - 使用案例(父进程和子进程指代进程):

我有一个应用程序,使用父进程来管理大量的子进程,这些子进程依赖于父进程的管理设施。为了完成其工作,父进程依赖于收到相关的SIGCHLD信号以通知子进程的终止。

如果父进程本身由于某种意外原因(包括段错误)而死亡,则需要重新启动整个“家族”,因为现在无法在子进程的终止上触发任何操作(这也可能是由于段错误)。

在这种情况下,我需要关闭所有子进程并重新启动整个系统。

避免此类情况的一种可能方法是放置一个备用进程,该进程可以接管已死亡的父进程的角色... - 如果它可以再次接收到后代SIGCHLD信号!


如果子进程只继承了父进程创建的管道的“读”端,那么它们可以自行退出。当父进程死亡时,“读”端将选择可读(对于EOF),每个子进程都可以捕获并做出反应。 - pilcrow
这个问题是我不想让子进程失控。我希望能够用一个类似继父进程的进程来取代已经死亡的父进程(如果父进程已经终止,则能够接收它们的SIGCHLD信号)。@pilcrow - alk
在我上面的评论中,"... receive their SIGCHLD ..." 应该改为 "... receive their children's SIGCHLD ..."。 - alk
4个回答

17
不可能实现。这也不能在没有一些恶意竞态条件的情况下实现。制作这些API的POSIX人员永远不会创建具有内在竞争条件的东西,因此即使您不介意,您的内核也不会很快得到它。
一个问题是pid会被重用(它们是稀缺资源!),而且您也无法获得对其的句柄或锁;它只是一个数字。那么,在代码的某个地方,您将pid放入变量中,以便将该过程的pid更改为当前过程的子进程的pid。然后您调用make_this_a_child_of_me(thepid)。那么会发生什么呢?同时,其他进程可能已经退出并且thepid已更改以引用其他过程!哎呀。除非大规模重组unix处理进程的方式,否则不可能提供make_this_a_child_of_me API的方法。
请注意,等待子PID的整个交易的问题就在于防止这个问题:僵尸进程仍存在于进程表中,以防止其pid被重用。父级可以通过其pid引用其子级,确信进程不会退出并且子pid不会被重用。如果子级退出,则其pid保留,直到父级捕获SIGCHLD或等待它。一旦进程被收割,其pid就可以立即供其他程序在fork时开始使用,但父级已经知道它。
对更新的回应:考虑一个更复杂的方案,其中进程将被重新分配给其下一个祖先。显然,这不能在所有情况下完成,因为您经常需要一种方法来放弃对子项的所有权,以确保避免僵尸进程。init非常好地履行了这一角色。因此,必须有一种方式使进程指定其是否打算采用或未采用其孙子(或更低版本)。这种设计的问题与第一个情况完全相同:您仍然会遇到竞态条件。如果再次按进程ID执行此操作,则祖父进程将面临竞争条件:只有父进程能够回收PID,因此只有父进程真正知道一个PID对应的是哪个子进程。由于祖父进程无法回收,它无法确定孙子进程是否已更改为其打算采用(或拒绝,具体取决于虚构API的工作方式)的进程。请记住,在负载繁重的机器上,进程可能会从CPU上下文中移出几分钟,这段时间内可能发生了整个变化!虽然不理想,但POSIX必须加以考虑。
最后,假设此API不通过PID而是一般性地说:“将所有孙子进程发送给我”或“将它们发送到init”。如果在生成子进程之后调用它,则仍会出现竞争条件。如果在生成子进程之前调用,则整个过程就毫无用处:您应该能够稍微重新组织应用程序以获得相同的行为。也就是说,如果在开始生成子进程之前就知道谁应该成为谁的父级,则为什么不能立即按正确顺序创建它们?管道和IPC确实能够完成所有所需的工作。

再次感谢您分享您的想法。关于将父级重新指定为祖先,我确实认为这不能成为默认行为,因为许多现有的实现(和模式)都依赖于子项失去其父项并被init采用。 - alk
由于init是一个进程,它会接受子进程(由内核执行),所以为什么不允许内核将这些孤儿分配给除init之外的其他进程呢?我确实在考虑一种方法,就像你在段落中提到的那样:“把所有孙子送给我”或“把它们送到init”。有关我面临的用例的详细信息,请参见我的问题中的更新-2。 - alk
好的观点。在Linux内核中,尽管子进程和父进程的task_struct相互保持指针,所以从技术上讲,当父进程死亡时,祖父进程可以继承一个孩子。 - Maxim Egorushkin
2
请注意,例如Linux(自内核3.4以来)除了“重新指定为init”之外,还有一种额外的方式可以重新指定进程的父进程,请参见https://dev59.com/SFjUa4cB1Zd3GeqPS53I,(但仍然无法再次从init中采用进程)。 - nos
@nos:非常感谢您指出这一点,因为这将涵盖我在问题第一个更新中提到的用例。 - alk
显示剩余5条评论

8
没有办法按照您描述的方式强制重新父子化。

虽然我很难接受你所写的内容,但我接受了你的回答...;-) - 无论如何,我想知道是否有任何好的理由不提供这样的可能性。还是因为没有人需要它,所以没有人实现它(至今)?@als - alk
@alk:如果你觉得难以接受,那就不要接受它,让它成为一个问号,或者加上赏金。 - Alok Save
1
你说得对。我会取消接受你的答案,这样也许会激励其他人对这个问题发表评论。@als - alk

0

我不知道有什么好方法来实现这个,但是拥有它的一个原因是,运行中的进程可以独立存在或向父进程添加功能。采纳将作为事件的结果,由(尚未)子进程知道,但父进程不知道。即将成为子进程的进程将向父进程发送信号。父进程将采纳(或不采纳)子进程。一旦成为父进程的一部分,父/子进程将能够对事件做出反应,而单独时两者都无法对事件做出反应。

这种停靠行为可以编码到应用程序中,但我不知道如何在实时环境中实现它。还有其他方法可以实现相同的功能。一个能够接受停靠子进程的父进程可以以新颖的方式扩展其功能,这是父进程以前不知道的。


0

虽然原始问题标记为unix,但在Linux上也有一种方法可以实现这一点,因此值得一提。这可以通过使用子进程来实现。当一个进程的父进程退出时,它将被最近的子进程祖先或init接管。因此,在您的情况下,您将通过prctl(PR_SET_CHILD_SUBREAPER)将进程C设置为子进程,并生成进程A,当进程A死亡时,进程B将被C接管。

在Linux上的另一种选择是在单独的PID命名空间中生成C,使其成为PID命名空间的init进程,因此可以在A死亡时接管其子进程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接