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

问题:创建守护程序时执行双叉的原因是什么?

我正在尝试在python中创建守护程序。我发现了以下问题,该问题中有一些我目前正在关注的很好的资源,但是我很好奇为什么需要双叉。我到处搜寻Google,发现有很多资源宣称有必要,但不是为什么。

有人提到这是为了防止守护程序获取控制终端。没有第二个分叉怎么办?有什么影响?

I’m trying to create a daemon in python. I’ve found the following question, which has some good resources in it which I am currently following, but I’m curious as to why a double fork is necessary. I’ve scratched around google and found plenty of resources declaring that one is necessary, but not why.

Some mention that it is to prevent the daemon from acquiring a controlling terminal. How would it do this without the second fork? What are the repercussions?


回答 0

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

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

因此,这是为了确保该守护程序重新绑定到init上(以防启动该守护程序的进程长期存在),并消除该守护程序重新获得控制tty的任何可能性。因此,如果这两种情况均不适用,那么一个叉子就足够了。“ Unix网络编程-Stevens ”对此有很好的介绍。

Looking at the code referenced in the question, the justification is:

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.

So it is to ensure that the daemon is re-parented onto init (just in case the process kicking off the daemon is long lived), and removes any chance of the daemon reacquiring a controlling tty. So if neither of these cases apply, then one fork should be sufficient. “Unix Network Programming – Stevens” has a good section on this.


回答 1

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

在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

请注意,该过程是之后的会话负责人Decouple#1,因为它是PID = SID。它仍然可以控制TTY。

请注意,Fork#2不再是会议负责人PID != SID。此过程永远无法控制TTY。真正守护。

我个人发现术语叉两次是令人困惑的。更好的习惯用法可能是前叉-后叉-叉子。

其他感兴趣的链接:

I was trying to understand the double fork and stumbled upon this question here. After a lot of research this is what I figured out. Hopefully it will help clarify things better for anyone who has the same question.

In Unix every process belongs to a group which in turn belongs to a session. Here is the hierarchy…

Session (SID) → Process Group (PGID) → Process (PID)

The first process in the process group becomes the process group leader and the first process in the session becomes the session leader. Every session can have one TTY associated with it. Only a session leader can take control of a TTY. For a process to be truly daemonized (ran in the background) we should ensure that the session leader is killed so that there is no possibility of the session ever taking control of the TTY.

I ran Sander Marechal’s python example daemon program from this site on my Ubuntu. Here are the results with my comments.

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

Note that the process is the session leader after Decouple#1, because it’s PID = SID. It could still take control of a TTY.

Note that Fork#2 is no longer the session leader PID != SID. This process can never take control of a TTY. Truly daemonized.

I personally find terminology fork-twice to be confusing. A better idiom might be fork-decouple-fork.

Additional links of interest:


回答 2

严格来说,双叉与将守护程序重新作为的子代无关init。重新给孩子父母父母所必须要做的就是父母必须退出。这仅需一个叉子即可完成。另外,仅靠自己做双叉也不会使守护进程重新成为父级init;守护程序的父项必须退出。换句话说,在派生适当的守护程序时,父级始终会退出,以便将守护程序进程重新绑定到init

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

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

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

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

…然后,取决于守护进程是否是会话领导者以及取决于系统实现,守护进程可能会获得/dev/console作为其控制终端的权限。如果程序首先确保它不是会话领导者,则该程序可以保证上述呼叫不会获得控制终端。

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

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

Strictly speaking, the double-fork has nothing to do with re-parenting the daemon as a child of init. All that is necessary to re-parent the child is that the parent must exit. This can be done with only a single fork. Also, doing a double-fork by itself doesn’t re-parent the daemon process to init; the daemon’s parent must exit. In other words, the parent always exits when forking a proper daemon so that the daemon process is re-parented to init.

So why the double fork? POSIX.1-2008 Section 11.1.3, “The Controlling Terminal“, has the answer (emphasis added):

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.

This tells us that if a daemon process does something like this …

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

… then the daemon process might acquire /dev/console as its controlling terminal, depending on whether the daemon process is a session leader, and depending on the system implementation. The program can guarantee that the above call will not acquire a controlling terminal if the program first ensures that it is not a session leader.

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.


回答 3

摘自Bad CTK

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

Taken from Bad CTK:

“On some flavors of Unix, you are forced to do a double-fork on startup, in order to go into daemon mode. This is because single forking isn’t guaranteed to detach from the controlling terminal.”


回答 4

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

According to “Advanced Programming in the Unix Environment”, by Stephens and Rago, the second fork is more a recommendation, and it is done to guarantee that the daemon does not acquire a controlling terminal on System V-based systems.


回答 5

原因之一是父进程可以立即为孩子创建wait_pid(),然后将其忽略。然后,当孙子去世时,它的父级是init,它将等待()-并将其带出僵尸状态。

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

One reason is that the parent process can immediately wait_pid() for the child, and then forget about it. When then grand-child dies, it’s parent is init, and it will wait() for it – and taking it out of the zombie state.

The result is that the parent process doesn’t need to be aware of the forked children, and it also makes it possible to fork long running processes from libs etc.


回答 6

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

它也可能基于一种错误的信念,即必须确保该守护进程没有父进程并被重新绑定到init,但是一旦父进程在单个fork实例中死亡,无论如何都会发生这种情况。

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

The daemon() call has the parent call _exit() if it succeeds. The original motivation may have been to allow the parent to do some extra work while the child is daemonizing.

It may also be based on a mistaken belief that it’s necessary in order to ensure the daemon has no parent process and is reparented to init – but this will happen anyway once the parent dies in the single fork case.

So I suppose it all just boils down to tradition in the end – a single fork is sufficient as long as the parent dies in short order anyway.


回答 7

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

从那里引用mlampkin:

…将setsid()调用视为做事​​的“新”方式(与终端解除关联),然后将[second] fork()调用作为处理SVr4的冗余方法…

A decent discussion of it appear to be at http://www.developerweb.net/forum/showthread.php?t=3025

Quoting mlampkin from there:

…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…


回答 8

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

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

It might be easier to understand in this way:

  • 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.