在创建守护进程时执行双分叉的原因是什么?

4dc9hkyq  于 2022-09-21  发布在  Unix
关注(0)|答案(9)|浏览(186)

我正在尝试用python创建一个守护进程。我已经找到了following question,其中有一些很好的资源,我现在正在关注它,但我很好奇为什么需要双叉。我在谷歌上搜了一遍,找到了大量的资源,宣称有必要这样做,但不知道为什么。

一些人提到,这是为了防止守护进程获取控制终端。如果没有第二个叉子,它怎么能做到这一点?其后果是什么?

c3frrgcw

c3frrgcw1#

我正试图理解双叉,在这里偶然发现了这个问题。经过大量的研究,这就是我想出来的。希望它能帮助任何有同样问题的人更好地澄清问题。

在Unix中,每个进程都属于一个组,而组又属于一个会话。下面是层次结构…

会话(SID)→进程组(PGID)→进程(PID)

进程组中的第一个进程成为进程组领导者,会话中的第一个进程成为会话领导者。每个会话可以有一个与之相关联的TTY。只有会话领导者才能控制TTY。对于一个真正守护进程(在后台运行)的进程,我们应该确保杀死会话领导者,这样会话就不可能控制TTY。

我在我的Ubuntu上从this site运行了Sander Marechal的python示例守护进程程序。以下是我的评论的结果。

1. `Parent`    = PID: 28084, PGID: 28084, SID: 28046
2. `Fork#1`    = PID: 28085, PGID: 28084, SID: 28046
3. `Decouple#1`= PID: 28085, PGID: 28085, SID: 28085
4. `Fork#2`    = PID: 28086, PGID: 28085, SID: 28085

请注意,该进程是Decouple#1之后的会话领导者,因为它是PID = SID。它仍有可能控制一家TTY。

请注意,Fork#2不再是会话领导者PID != SID。这一过程永远无法控制TTY。真正的守护神。

我个人觉得术语分叉--两次--令人困惑。一个更好的习惯用法可能是分叉-分叉-分叉。

感兴趣的其他链接:

50few1ms

50few1ms2#

严格地说,双叉与将守护进程重新设置为init的子级没有任何关系。要重新为孩子设置父对象,所有需要做的就是父对象必须退出。这可以只用一把叉子来完成。此外,单独执行双分支不会将守护进程重新设置为init的父进程;守护进程的父进程必须退出。换句话说,当派生适当的守护进程时,父进程总是退出,因此守护进程被重新设置为init的父进程。

那么为什么要用双叉子呢?POSIX.1-2008第11.1.3节“控制终端”给出了答案(增加了重点):
会话的控制终端由会话领导者以实现定义的方式进行分配。如果会话引导者没有控制终端,并且在没有使用O_NOCTTY选项(参见open())的情况下打开尚未与会话相关联的终端设备文件,则该终端是否成为会话引导者的控制终端由实现定义。如果非会话领导者的进程打开了终端文件,或者open()上使用了O_NOCTTY选项,则该终端不会成为呼叫进程的控制终端

这告诉我们,如果守护进程执行以下操作...

int fd = open("/dev/console", O_RDWR);

..。然后,守护进程可能获取/dev/console作为其控制终端,这取决于该守护进程是否是会话领导者,并且取决于系统实现。如果程序首先确保上述呼叫不是会话引导者,则该程序可以*保证“不会获得”控制终端。

通常,在启动守护程序时,会调用setsid(在调用fork之后从子进程中),以将该守护程序与其控制终端分离。然而,调用setsid也意味着调用进程将成为新会话的会话主导者,这使得守护进程有可能重新获取控制终端。Double-fork技术确保守护进程不是会话领导者,这就保证了对open的调用不会导致守护进程重新获取控制终端。

双叉技术有点偏执。如果您知道守护进程永远不会打开终端设备文件,则可能没有必要。此外,在某些系统上,即使守护进程打开了终端设备文件,也可能没有必要这样做,因为该行为是由实现定义的。然而,有一件事不是由实现定义的,那就是只有会话领导者才能分配控制终端。**如果进程不是会话领导者,则不能分配控制终端。**因此,如果您想要疑神疑鬼,并确保守护进程不会无意中获取控制终端,无论任何实现定义的细节,那么双叉技术是必不可少的。

mrfwxfqh

mrfwxfqh3#

查看问题中引用的代码,理由是:
分叉第二个孩子,并立即退出,以防止僵尸。这会导致第二个子进程成为孤立进程,使init进程负责其清理工作。而且,因为第一个孩子是没有控制终端的会话领导者,所以它有可能在将来通过打开终端(基于系统V的系统)来获得一个控制终端。第二个派生保证该子进程不再是会话领导者,从而防止守护进程获取控制终端。

因此,这样做是为了确保守护进程重新成为init的父进程(以防启动守护进程的进程持续时间较长),并消除守护进程重新获得控制tty的任何机会。因此,如果这两种情况都不适用,那么一个叉子应该就足够了。“Unix Network Programming - Stevens”在这方面有一个很好的章节。

jw5wzhpr

jw5wzhpr4#

摘自Bad CTK

在某些版本的Unix上,为了进入守护程序模式,您必须在启动时执行双分支。这是因为不能保证单分支与控制终端分离。

hgb9j2n6

hgb9j2n65#

根据Stephens和Rago的《Unix Environment中的高级编程》,第二个分支更多的是一种推荐,这样做是为了保证守护进程不会在基于System V的系统上获取控制终端。

rnmwe5a2

rnmwe5a26#

原因之一是父进程可能会立即等待该子进程的WAIT_PID(),然后将其忘记。当孙子去世时,它的父母是init,它将等待()-并使它脱离僵尸状态。

结果是父进程不需要知道派生的子进程,它还使从库等派生长时间运行的进程成为可能。

sd2nnvve

sd2nnvve7#

如果成功,守护程序()调用将拥有父Call_Exit()。最初的动机可能是让父母在孩子处于守护神状态时做一些额外的工作。

这也可能是基于一种错误的信念,即为了确保守护进程没有父进程并重新设置为init,这是必要的-但在单叉情况下,一旦父进程死亡,这种情况无论如何都会发生。

所以我想这一切归根结底都是传统--只要父母在短时间内死亡,一把叉子就足够了。

vql8enpb

vql8enpb8#

关于它的一个像样的讨论似乎在http://www.developerweb.net/forum/showthread.php?t=3025

引用Mlampkin的话:
...将setsid()调用视为处理(与终端分离)的“新”方法,并将其之后的[第二]fork()调用视为处理SVR4的冗余...

zhte4eai

zhte4eai9#

这样可能更容易理解:

  • 第一个fork和setsid将创建一个新会话(但进程ID==会话ID)。
  • 第二个分支确保进程ID!=会话ID。

相关问题