我试图在python中创建一个守护进程。我发现了下面的问题,其中有一些很好的资源,我目前正在关注,但我很好奇为什么需要双分叉。我对谷歌进行了研究,发现了大量的资源,这些资源都表明这是必要的,但不知道为什么。
有人提到这是为了防止守护进程获取控制终端。如果没有第二个分叉,它将如何做到这一点?后果是什么?
我试图在python中创建一个守护进程。我发现了下面的问题,其中有一些很好的资源,我目前正在关注,但我很好奇为什么需要双分叉。我对谷歌进行了研究,发现了大量的资源,这些资源都表明这是必要的,但不知道为什么。
有人提到这是为了防止守护进程获取控制终端。如果没有第二个分叉,它将如何做到这一点?后果是什么?
当前回答
我试图理解这个双叉,无意中发现了这个问题。经过大量研究,我得出了这个结论。希望它能帮助那些有同样问题的人更好地澄清事情。
在Unix中,每个进程都属于一个组,而这个组又属于一个会话。这是层次结构……
会话(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
请注意,该进程是在脱钩#1之后的会话领导者,因为它是PID = SID。它仍然可以控制TTY。
注意,Fork#2不再是会话领导PID != SID。这个过程永远不能控制TTY。真正的监控。
我个人认为术语fork-twice令人困惑。一个更好的习语可能是fork- decoupling -fork。
其他感兴趣的链接:
Unix进程- http://www.win.tue.nl/~aeb/linux/lk/lk-10.html
其他回答
严格地说,双分叉与将守护进程作为init的子进程重新养育无关。重父进程所需要的就是父进程必须退出。这只需要一个分叉就可以完成。另外,单独执行double-fork并不会将守护进程重新父化给init;守护进程的父进程必须退出。换句话说,当派生一个正确的守护进程时,父进程总是退出,以便守护进程被重新父化到init。
那么为什么要用双叉呢?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.
这告诉我们,如果一个守护进程做了这样的事情…
int fd = open("/dev/console", O_RDWR);
... 然后,守护进程可能会获取/dev/console作为其控制终端,这取决于守护进程是否是会话领导者,以及系统实现。如果程序首先确定控制终端不是会话领导终端,则程序可以保证上述调用不会获取控制终端。
Normally, when launching a daemon, setsid is called (from the child process after calling fork) to dissociate the daemon from its controlling terminal. However, calling setsid also means that the calling process will be the session leader of the new session, which leaves open the possibility that the daemon could reacquire a controlling terminal. The double-fork technique ensures that the daemon process is not the session leader, which then guarantees that a call to open, as in the example above, will not result in the daemon process reacquiring a controlling terminal.
The double-fork technique is a bit paranoid. It may not be necessary if you know that the daemon will never open a terminal device file. Also, on some systems it may not be necessary even if the daemon does open a terminal device file, since that behavior is implementation-defined. However, one thing that is not implementation-defined is that only a session leader can allocate the controlling terminal. If a process isn't a session leader, it can't allocate a controlling terminal. Therefore, if you want to be paranoid and be certain that the daemon process cannot inadvertently acquire a controlling terminal, regardless of any implementation-defined specifics, then the double-fork technique is essential.
如果daemon()调用成功,则有父调用_exit()。最初的动机可能是让父母在孩子守护的时候做一些额外的工作。
这也可能是基于一种错误的信念,认为这是必要的,以确保守护进程没有父进程,并被重新父化给init——但在单分叉的情况下,一旦父进程死亡,无论如何都会发生这种情况。
所以我认为这一切最终都归结为传统——只要父母在短时间内死亡,一个分叉就足够了。
根据Stephens和Rago的“Unix环境中的高级编程”,第二个fork更多的是一个建议,它是为了保证守护进程不会在基于System v的系统上获得控制终端。
这样也许更容易理解:
第一个fork和setsid将创建一个新的会话(但是进程ID ==会话ID)。 第二个fork确保进程ID !=会话ID。
一个原因是父进程可以立即为子进程wait_pid(),然后忘记它。当孙子孙女死亡时,它的父节点是init,它将wait()等待它——并将它从僵尸状态中取出。
结果是,父进程不需要知道被分叉的子进程,这也使得从库等中分叉长时间运行的进程成为可能。