关于python:创建守护进程时执行双叉的原因是什么?

What is the reason for performing a double fork when creating a daemon?

我正在尝试用python创建一个守护进程。我发现了下面的问题,其中有一些很好的资源,我目前正在关注,但我很好奇为什么需要一个双叉。我在Google上搜刮了一下,发现有很多资源声称有必要,但不是为什么。

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


我试图理解这个双叉问题,却在这里偶然发现了这个问题。经过大量的研究,我发现了这一点。希望它能帮助任何有相同问题的人更好地澄清问题。

在UNIX中,每个进程都属于一个组,而该组又属于一个会话。这是层次结构…

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

过程组中的第一个过程成为过程组组长,会话中的第一个过程成为会话组长。每个会话都可以有一个与之关联的tty。只有会议主持人才能控制TTY。为了真正实现进程的后台监控(在后台运行),我们应该确保会话领导人被杀死,这样会话就不可能控制TTY。

我在Ubuntu上的这个站点运行了sander marechal的python示例守护程序。以下是我的评论结果。

1
2
3
4
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-decouplefork。

其他感兴趣的链接:

  • Unix进程-http://www.win.tue.nl/~aeb/linux/lk/lk-10.html


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

Fork a second child and exit immediately to prevent zombies. This
causes the second child process to be orphaned, making the init
process responsible for its cleanup. And, since the first child is
a session leader without a controlling terminal, it's possible for
it to acquire one by opening a terminal in the future (System V-
based systems). This second fork guarantees that the child is no
longer a session leader, preventing the daemon from ever acquiring
a controlling terminal.

因此,要确保该守护进程重新成为in it的父进程(以防启动该守护进程的进程长期存在),并消除该守护进程重新获取控制tty的任何机会。因此,如果这两种情况都不适用,那么一个分叉就足够了。"Unix网络编程-Stevens"对此有一个很好的部分。


严格地说,双叉与将守护进程重新作为init的子代没有任何关系。重新设置子对象的父对象所必需的就是父对象必须退出。这只能用一个叉子来完成。此外,单独执行一个双分叉并不将守护进程作为init的父进程;守护进程的父进程必须退出。换言之,当分叉一个适当的守护进程时,父进程总是退出,以便守护进程重新成为EDOCX1的父进程(0)。

那么为什么要用双叉呢?POSIX.1-2008第11.1.3节"控制终端"给出了答案(重点补充):

The controlling terminal for a session is allocated by the session leader in an implementation-defined manner. If a session leader has no controlling terminal, and opens a terminal device file that is not already associated with a session without using the O_NOCTTY option (see open()), it is implementation-defined whether the terminal becomes the controlling terminal of the session leader. If a process which is not a session leader opens a terminal file, or the O_NOCTTY option is used on open(), then that terminal shall not become the controlling terminal of the calling process.

这告诉我们,如果一个守护进程执行类似的操作…

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

…然后,守护进程可能会获取/dev/console作为其控制终端,这取决于守护进程是否是会话领导人,以及取决于系统实现。如果程序首先保证它不是会话引导程序,则程序可以保证上述调用不会获得控制终端。

通常,在启动守护进程时,调用setsid(从调用fork之后的子进程中调用)以将守护进程与其控制终端分离。但是,调用setsid也意味着调用进程将是新会话的会话领导人,这使得守护进程可以重新获取控制终端的可能性大大增加。双分叉技术确保守护进程不是会话领队,然后保证调用open不会导致守护进程重新获得控制终端。

双叉技术有点偏执。如果您知道守护程序永远不会打开终端设备文件,则可能不需要。此外,在某些系统上,即使守护进程打开了终端设备文件,也可能不需要,因为该行为是实现定义的。但是,有一件事没有定义实现,那就是只有会话领导人才能分配控制终端。如果进程不是会话引导程序,它就不能分配控制终端。因此,如果您想要偏执并且确信守护进程不会无意中获得控制终端,不管实现定义了什么细节,那么双分叉技术是必不可少的。


取自坏CTK:

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


根据Stephens和Rago的"Unix环境中的高级编程",第二个fork更像是一个建议,它是为了保证守护进程不会在基于系统V的系统上获得控制终端。


一个原因是父进程可以立即等待子进程的pid(),然后忽略它。当孙子死后,它的父级是init,它将等待(),并将其从僵尸状态中取出。

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


在http://www.developerweb.net/forum/showthread.php上有一个不错的讨论?t=3025

从这里引用姆兰普金:

...think of the setsid( ) call as the"new" way to do thing (disassociate from the terminal) and the [second] fork( ) call after it as redundancy to deal with the SVr4...


daemon()调用在成功时具有父调用exit()。最初的动机可能是允许家长在孩子进行守护进程时做一些额外的工作。

它也可能是基于一个错误的信念,即为了确保守护进程没有父进程并重新准备为in it,这是必要的,但一旦父进程在单个fork情况下死亡,这种情况就会发生。

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


这样可能更容易理解:

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