首页 文章

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

提问于
浏览
147

我已经找到了following question,它有一些很好的资源,我目前正在关注它,但是我在谷歌周围发现了很多资源,声称有必要,但不是为什么 .

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

9 回答

  • 3

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

    • 第一个fork和setsid将创建一个新会话(但进程ID ==会话ID) .

    • 第二个分支确保进程ID!=会话ID .

  • 153

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

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

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

  • 96

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

    在Unix中,每个进程都属于一个组,而该组又属于一个会话 . 这是层次结构......

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

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

    我在我的Ubuntu上从this site运行了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-decouple-fork .

    其他感兴趣的链接:

  • 8

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

    那么为什么双叉呢? POSIX.1-2008第11.1.3节“The Controlling Terminal”有答案(重点补充):

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

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

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

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

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

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

  • 102

    取自Bad CTK

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

  • 2

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

  • 2

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

    结果是父进程不需要知道分叉的子进程,它还可以从libs等分叉长时间运行的进程 .

  • 11

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

    它也可能基于一种错误的信念,即为了确保守护进程没有父进程并且被重新设置为init是必要的 - 但是一旦父进程在单fork情况下死亡,这种情况就会发生 .

    所以我想这一切最终归结为传统 - 只要父母在短期内死亡,单个分叉就足够了 .

  • 0

    对它的体面讨论似乎是在http://www.developerweb.net/forum/showthread.php?t=3025

    从那里引用mlampkin:

    ...将setsid()调用视为事物的“新”方式(与终端解除关联)和[second] fork()调用之后作为处理SVr4的冗余...

相关问题