192

我正在尝试在 python 中创建一个守护进程。我发现了以下问题,其中有一些我目前正在关注的好资源,但我很好奇为什么需要双叉。我在 google 上四处搜寻,发现大量资源声明这是必要的,但不是为什么。

有人提到这是为了防止守护进程获取控制终端。如果没有第二个分叉,它将如何做到这一点?有什么影响?

4

9 回答 9

186

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

在 Unix 中,每个进程都属于一个组,该组又属于一个会话。这是层次结构……</p>

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

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

我在我的 Ubuntu 上从这个站点运行了 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-two 令人困惑。更好的习惯用法可能是 fork-decouple-fork。

其他感兴趣的链接:

于 2011-03-22T04:19:44.600 回答
138

严格来说,双叉与将守护进程重新设置为init. 重新养育孩子所需要的只是父母必须退出。这可以只用一个叉子来完成。此外,单独执行双叉不会将守护进程重新设置为init; 守护进程的父进程必须退出。换句话说,当派生一个适当的守护进程时,父进程总是退出,以便守护进程重新成为init.

那么为什么是双叉呢?POSIX.1-2008第 11.1.3 节“控制终端”有答案(强调添加):

会话的控制终端由会话领导者以实现定义的方式分配。O_NOCTTY如果会话领导者没有控制终端,并且在不使用选项(请参阅 参考资料)的情况下打开尚未与会话关联的终端设备文件,则open()终端是否成为会话领导者的控制终端是实现定义的。如果一个不是会话领导者的进程打开了一个终端文件,或者该O_NOCTTY选项被使用 on open()则该终端不应成为调用进程的控制终端

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

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

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

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

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

于 2013-05-01T11:54:52.977 回答
119

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

分叉第二个孩子并立即退出以防止僵尸。这会导致第二个子进程成为孤立的,使 init 进程负责其清理。而且,由于第一个孩子是没有控制终端的会话领导者,因此它有可能通过将来打开终端来获得一个(基于 System V 的系统)。第二个分叉保证子进程不再是会话领导者,从而阻止守护进程获取控制终端。

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

于 2009-05-19T07:42:54.490 回答
11

取自坏 CTK

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

于 2009-05-19T07:32:26.770 回答
7

根据 Stephens 和 Rago 撰写的“Unix 环境中的高级编程”,第二个 fork 更多的是推荐,这样做是为了保证守护程序不会在基于 System V 的系统上获取控制终端。

于 2009-05-19T07:33:54.723 回答
3

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

结果是父进程不需要知道分叉的子进程,并且还可以从库等分叉长时间运行的进程。

于 2009-05-19T07:43:39.513 回答
2

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

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

所以我想这一切最终都归结为传统——只要父母在短时间内死亡,一个叉子就足够了。

于 2009-05-19T07:33:16.037 回答
2

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

从那里引用mlampkin:

...将 setsid( ) 调用视为做事​​的“新”方式(与终端分离),然后将 [second] fork( ) 调用视为处理 SVr4 的冗余...

于 2009-05-19T07:56:23.810 回答
1

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

  • The first fork and setsid will create a new session (but the process ID == session ID).
  • The second fork makes sure the process ID != session ID.
于 2018-08-28T03:15:02.643 回答