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

Sha*_*obe 156 python unix daemon

我正在尝试在python中创建一个守护进程.我发现了以下问题,其中有一些我目前正在关注的好资源,但我很好奇为什么需要双叉.我抓住谷歌,发现有足够的资源声明一个是必要的,但不是为什么.

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

Pra*_*ota 162

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

在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
Run Code Online (Sandbox Code Playgroud)

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

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

我个人觉得术语分叉两次令人困惑.一个更好的习语可能是fork-decouple-fork.

其他感兴趣的链接:

  • 分叉两次还可以防止当父进程运行较长时间时创建僵尸,并且由于某种原因删除通知该进程死亡的信号的默认处理程序。 (3认同)
  • 这不是真的。只要您关闭父级,第一个`fork()`就可以防止创建僵尸。 (2认同)
  • 在单个“fork()”之前调用“setsid()”会有什么好处吗?实际上我猜[这个问题](http://stackoverflow.com/q/2613104/60075)的答案回答了这个问题。 (2认同)

Dan*_*ing 106

严格来说,双叉与将守护进程重新作为子进程无关init.重新生成孩子所需要的只是父母必须退出.这可以只使用一个fork来完成.另外,单独执行双分叉并不会将守护进程重新为父进程init; 守护进程的父进程必须退出.换句话说,在分配正确的守护进程时父进程总是退出,以便守护进程重新成为父进程init.

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

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

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

int fd = open("/dev/console", O_RDWR);
Run Code Online (Sandbox Code Playgroud)

...然后守护进程可能获取O_NOCTTY作为其控制终端,具体取决于守护进程是否是会话负责人,并取决于系统实现.如果程序首先确保它不是会话领导者,则程序可以保证上述呼叫不会获得控制终端.

通常,在启动守护程序时,open()会调用(从调用后的子进程O_NOCTTY)将守护程序与其控制终端分离.但是,调用open()也意味着调用进程将是新会话的会话负责人,这使守护进程可能重新获取控制终端.双叉技术确保守护进程不是会话负责人,然后保证调用/dev/console,如上例所示,不会导致守护进程重新获取控制终端.

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

  • 但这仍然无法解释为什么守护程序无法重新获取控制终端的重要性 (10认同)
  • @Marius想象一下,如果你向守护进程的配置文件中添加这样的行,可能会发生什么:`LogFile =/dev/console`.程序并不总是对它们可能打开的文件进行编译时控制;) (9认同)
  • 关键字是"无意中"获取控制终端.如果进程碰巧打开终端,并且它成为进程控制终端,那么如果有人从该终端发出^ C,它可以终止该进程.因此,保护​​一个进程不会无意中发生这种情况可能会很好.就个人而言,我会坚持使用一个fork和setsid()来编写我知道不会打开终端的代码. (7认同)
  • +1这个答案太差了〜问题提出四年后. (3认同)

Bea*_*ano 101

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

__PRE__

因此,确保将守护进程重新设置为init(以防止守护进程长时间运行),并消除守护进程重新获取控制tty的任何可能性.因此,如果这两种情况都不适用,那么一个分叉就足够了." Unix网络编程 - 史蒂文斯 "有一个很好的部分.

  • 这不完全正确.创建守护进程的标准方法是简单地执行`p = fork(); if(p)exit(); setsid()`.在这种情况下,父级也退出,并且第一个子进程被重新分配.双叉魔法仅**是防止守护进程获取tty所必需的. (24认同)
  • @tonix:简单地分叉不会创建会话领导者。这是由`setsid()` 完成的。因此,在调用 setsid() 之后,第一个分叉的进程成为会话领导者,然后我们再次分叉,使最终的双分叉进程不再是会话领导者。除了“setsid()”作为会话负责人的要求之外,您还可以。 (4认同)

Ste*_*erg 11

取自坏CTK:

"在Unix的某些版本中,你被迫在启动时进行双分叉,以便进入守护进程模式.这是因为单个分叉不能保证从控制终端分离."

  • 守护进程必须关闭它的输入和输出文件描述符(fds),否则它仍然会附加到它启动的终端.分叉进程继承父进程.显然,第一个孩子关闭了fds,但这并不能清理所有东西.在第二个分支上,fds不存在,因此第二个孩子不能再连接任何东西了. (12认同)
  • @Aaron:不,守护进程通过在初始分支后调用`setsid`来正确地从控制终端"分离"自己.然后通过再次分叉并使会话负责人(称为`setsid`的进程)退出,确保它与控制终端保持**分离. (4认同)
  • 单叉如何不从控制终端分离但是双叉这样做?发生了什么unixes? (3认同)
  • @bdonlan:与控制终端脱离的不是"fork".它就是`setsid`.但是,如果从进程组领导调用它,`setsid`将会失败.因此,必须在`setsid`之前完成一个初始的`fork`,以确保从一个不是进程组领导者的进程调用`setsid`.第二个`fork`确保最终进程(将成为守护进程)不是会话负责人.只有会话负责人可以获得一个控制终端,所以这个第二个分支保证守护进程不会无意中重新获取控制终端.任何POSIX OS都是如此. (2认同)

Pao*_*sco 7

根据Stephens和Rago的"Unix环境中的高级编程",第二个分支是一个推荐,它是为了保证守护进程不在基于System V的系统上获得控制终端.