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

发布于 2024-07-21 13:21:25 字数 268 浏览 11 评论 0原文

我正在尝试用 python 创建一个守护进程。 我发现了以下问题,它有一些好处我目前正在关注其中的资源,但我很好奇为什么需要双叉。 我在谷歌上搜索了一下,发现很多资源都声称这是必要的,但没有说明原因。

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

给我一枪 2024-07-28 13:21:25

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

在 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

请注意,该进程是 De Couple#1 之后的会话领导者,因为它是 PID = SID。 它仍然可以控制 TTY。

请注意,Fork#2 不再是会话领导者 PID != SID。 此进程永远无法控制 TTY。 真正的守护进程。

我个人认为术语 fork-twice 很令人困惑。 更好的习惯用法可能是 fork-de Couple-fork。

其他感兴趣的链接:

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:

审判长 2024-07-28 13:21:25

严格来说,双分叉与将守护进程重新设置为 init 的子进程无关。 重新设置子进程的父级所需要的只是父级必须退出。 只需使用一个叉子即可完成此操作。 此外,单独执行双分叉不会将守护进程重新设置为 init; 守护进程的父进程必须退出。 换句话说,当分叉适当的守护进程时,父进程总是退出,以便守护进程重新成为 init 的父进程。

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

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

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

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.

醉酒的小男人 2024-07-28 13:21:25

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

分叉第二个孩子并立即退出以防止僵尸。 这
导致第二个子进程成为孤立进程,从而使 init
负责其清理的进程。 而且,由于第一个孩子是
没有控制终端的会话领导者,可以
它可以通过在未来打开一个终端来获取一个(系统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.

宫墨修音 2024-07-28 13:21:25

取自 错误的 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."

逆流 2024-07-28 13:21:25

根据 Stephens 和 Rago 所著的《Unix 环境中的高级编程》,第二个分支更多的是一个建议,这样做是为了保证守护进程不会在基于 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.

岁月染过的梦 2024-07-28 13:21:25

这样可能更容易理解:

  • 第一次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.
半寸时光 2024-07-28 13:21:25

原因之一是父进程可以立即为子进程 wait_pid(),然后就忘记它。 当孙子死亡时,它的父级处于 init 状态,并且它将 wait() 等待它 - 并将其带出僵尸状态。

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

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.

暮年 2024-07-28 13:21:25

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

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

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

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.

指尖凝香 2024-07-28 13:21:25

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

从那里引用 mlampkin :

...将setsid( ) 调用视为执行操作的“新”方式(与终端断开关联),并将其后的[第二个] fork( ) 调用视为处理 SVr4 的冗余...< /p>

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文