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

时间:2009-05-19 07:25:18

标签: python unix daemon

我正在尝试在python中创建一个守护进程。我找到了following question,它有一些我目前正在关注的好资源,但我很好奇为什么需要双叉。我抓住谷歌,发现有足够的资源宣称一个是必要的,但不是为什么。

有人提到它是为了防止守护进程获取控制终端。没有第二个叉子怎么做呢?有什么影响?

9 个答案:

答案 0 :(得分:156)

我试图理解双叉,并在这里偶然发现了这个问题。经过大量的研究,这才是我想到的。希望它能帮助那些有同样问题的人更好地澄清事情。

在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。 真正守护。

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

其他感兴趣的链接:

答案 1 :(得分:102)

查看问题中引用的代码,理由是:

  

分叉第二个孩子并立即退出以防止僵尸。这个       导致第二个子进程成为孤儿,进行初始化       负责清理的过程。而且,因为第一个孩子是       没有控制终端的会话负责人,有可能       通过在未来开设终端获得一个(系统V-       基于系统)。这第二个叉子保证孩子不行       更长的会话负责人,阻止守护进程获取       控制终端。

因此,确保将守护进程重新设置为init(以防万一启动守护进程的过程很长时间),并删除守护进程重新获取控制tty的任何机会。因此,如果这两种情况都不适用,那么一个分叉就足够了。 “Unix Network Programming - Stevens”对此有一个很好的部分。

答案 2 :(得分:98)

严格地说,双叉与将守护进程重新作为init的子进程无关。重新生成孩子所需要的只是父母必须退出。这可以只使用一个fork来完成。此外,单独执行双分叉不会将守护进程重新父级转移到init;守护进程的父必须退出。换句话说,在分配正确的守护进程时父进程总是退出,以便将守护进程重新设置为init

那为什么双叉呢? POSIX.1-2008第11.1.3节“The Controlling Terminal”有答案(强调补充):

  

会话的控制终端由会话负责人以实现定义的方式分配。如果会话负责人没有控制终端,并且在不使用O_NOCTTY选项的情况下打开尚未与会话关联的终端设备文件(请参阅open()),则终端变为实现定义会话负责人的控制终端。如果非会话负责人的流程打开终端文件,或O_NOCTTY上使用open()选项,则该终端不会成为控制终端调用过程

这告诉我们如果一个守护进程做了这样的事情......

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

...然后守护进程可能获取/dev/console作为其控制终端,具体取决于守护程序进程是否为会话负责人,具体取决于系统实现。如果程序首先确保它不是会话领导者,则程序可以保证以上呼叫不会获得控制终端。

通常,在启动守护程序时,会调用setsid(在调用fork之后从子进程调用)以将守护程序与其控制终端分离。但是,调用setsid也意味着调用进程将成为新会话的会话负责人,这使守护进程可能重新获取控制终端。双叉技术确保守护进程不是会话负责人,然后保证对open的调用(如上例所示)不会导致守护进程重新获取控制终端。

双叉技术有点偏执。如果您知道守护程序永远不会打开终端设备文件,则可能没有必要。此外,在某些系统上,即使守护程序确实打开了终端设备文件,也可能没有必要,因为该行为是实现定义的。但是,没有实现定义的一件事是只有会话负责人才能分配控制终端。 如果进程不是会话负责人,则无法分配控制终端。因此,如果您想要偏执并确定守护程序进程无法无意中获取控制终端,则无论任何实现定义的细节,然后双叉技术是必不可少的。

答案 3 :(得分:11)

取自Bad CTK

“在某些版本的Unix上,你被迫在启动时进行双分叉,以便进入守护进程模式。这是因为单个分叉不能保证从控制终端分离。”

答案 4 :(得分:7)

根据Stephens和Rago的“Unix环境中的高级编程”,第二个分支是一个推荐,它是为了保证守护进程不在基于System V的系统上获得控制终端。

答案 5 :(得分:3)

一个原因是父进程可以立即为子进程wait_pid(),然后忘记它。当那个盛大的孩子去世时,它的父母是init,它将等待()为它 - 并将其从僵尸状态中取出。

结果是父进程不需要知道分叉的子进程,它也可以从libs等分叉长时间运行的进程。

答案 6 :(得分:2)

如果daemon()调用成功,则调用父调用_exit()。最初的动机可能是允许父母在孩子守护的时候做一些额外的工作。

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

所以我认为这一切最终归结为传统 - 只要父母在短期内死亡,单个分叉就足够了。

答案 7 :(得分:2)

对它的体面讨论似乎在http://www.developerweb.net/forum/showthread.php?t=3025

从那里引用mlampkin:

  

...想想setsid()调用作为“新的”做事情的方式(与终端分离)和[second] fork()调用之后作为冗余来处理SVr4 ... < / p>

答案 8 :(得分:-1)

通过这种方式可能更容易理解:

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