为啥 fork() 两次[重复]

Posted

技术标签:

【中文标题】为啥 fork() 两次[重复]【英文标题】:Why fork() twice [duplicate]为什么 fork() 两次[重复] 【发布时间】:2012-06-11 13:17:07 【问题描述】:

Nagios 让我配置child_processes_fork_twice=<0/1>

documentation 说

此选项决定 Nagios 在执行主机和服务检查时是否会 fork() 子进程两次。默认情况下,Nagios fork()s 两次。但是,如果启用了 use_large_installation_tweaks 选项,它只会 fork() 一次。

据我所知fork() 将产生一个新的子进程。 我为什么要这样做两次?

【问题讨论】:

@larsmans 谢谢你的链接。当我首先研究这个问题时,它不在我的清单上。我刚刚在那里学到了很多东西并投了一些赞成票。 【参考方案1】:

这段代码演示了如何使用 double fork 方法让孙子进程被 init 采用,而不会有僵尸进程的风险。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

int main()

    pid_t p1 = fork();

    if (p1 != 0)
    
        printf("p1 process id is %d", getpid());
        wait();
        system("ps");
    
    else
    
        pid_t p2 = fork();
        int pid = getpid();

        if (p2 != 0) 
        
            printf("p2 process id is %d", pid);
        
        else
        
            printf("p3 process id is %d", pid);
        

        exit(0);
    

父进程将fork 新的子进程,然后wait 让它完成。子进程将fork 孙进程,然后exit(0)

在这种情况下,孙子进程除了exit(0) 之外什么都不做,但可以让守护进程执行您希望守护进程执行的任何操作。孙子可能寿命很长,完成后将由init 进程回收。

【讨论】:

An explanation would be nice. Op 实际上是在问为什么程序要像你写的那样写 @MichaelGaskill 感谢您的编辑。我会删除我的评论。【参考方案2】:

好的,那么现在首先:什么是僵尸进程?这是一个已经死亡的进程,但是它的父进程忙于做一些其他的工作,因此它无法收集孩子的退出状态. 在某些情况下,子进程运行了很长时间,父进程不能等待那么久,并且会继续它的工作(注意父进程并没有死,而是继续其剩余的任务但不会不关心孩子)。 这样就创建了一个僵尸进程。 现在让我们开始吧。两次分叉在这里有什么帮助? 需要注意的重要一点是,孙进程执行父进程希望其子进程执行的工作。 现在第一次调用 fork,第一个子进程只是再次分叉并退出.这样,父母不必等待很长时间才能收集孩子的退出状态(因为孩子唯一的工作就是创建另一个孩子并退出)。所以,第一个孩子不会变成僵尸。 至于孙子,它的父母已经死了。因此,孙子进程将被init 进程采用,该进程始终收集其所有子进程的退出状态。所以,现在父进程不用等待很长时间,也不会创建僵尸进程。 还有其他方法可以避免僵尸进程;这只是一种常见的技术。 希望这会有所帮助!

【讨论】:

啊。关系很复杂。而init是一个善良的老灵魂收养孙子。【参考方案3】:

在 Linux 中,守护进程通常通过两次 fork 来创建,中间进程在 fork 孙子进程后退出。这具有孤立孙进程的效果。因此,如果它终止,则操作系统有责任在它之后进行清理。原因与所谓的僵尸进程有关,它们在退出后继续存活并消耗资源,因为通常负责清理的父进程也已死亡。

【讨论】:

我看不出这比只分叉一次更好。我认为真正的原因与会话和控制终端有关,而不是孤立,但我可能弄错了...... 主要原因是如果您在登录会话中启动守护进程,双叉将使守护进程以 init (pid 1) 作为其父进程,并且当您注销会话时,SIGHUP不会杀死进程。它应该与僵尸进程无关,因为通常僵尸进程的主要原因是父进程在终止的子进程上没有“wait()”,并且操作系统保持子进程的返回值,等待让它的父母得到。所以在僵尸进程中,该进程已经退出,但由操作系统保留,因此并没有真正死亡,因此得名僵尸。【参考方案4】:

Unix Programming Faq§1.6.2:

1.6.2 如何防止它们发生?

您需要确保您的父进程调用wait()(或 waitpid()wait3() 等)对于每个终止的子进程; 或者,在某些系统上,您可以指示系统您是 对子退出状态不感兴趣。

另一种方法是fork() 两次,并有直系孩子 进程直接退出。这会导致孙子进程 孤儿,所以init进程负责清理它。为了 执行此操作的代码,请参阅示例部分中的函数fork2()

要忽略子退出状态,您需要执行以下操作(检查您的 系统的手册页看看这是否有效):

     struct sigaction sa;
     sa.sa_handler = SIG_IGN;
 #ifdef SA_NOCLDWAIT
     sa.sa_flags = SA_NOCLDWAIT;
 #else
     sa.sa_flags = 0;
 #endif
     sigemptyset(&sa.sa_mask);
     sigaction(SIGCHLD, &sa, NULL);

如果成功,则阻止 wait() 函数 在职的;如果他们中的任何一个被调用,他们将等到 all 孩子 进程已终止,然后返回失败并返回 errno == ECHILD

另一种技术是捕获 SIGCHLD 信号,并让 信号处理程序调用waitpid()wait3()。请参阅示例部分 一个完整的程序。

【讨论】:

只有当父进程在子进程(或孙子进程)启动后仍然存在时才有意义。如果整个程序是守护进程并且父进程无论如何都会立即退出,我认为双叉没有任何好处,至少在孤立进程方面没有。【参考方案5】:

同样来自documentation,

通常 Nagios 在执行主机和服务检查时会 fork() 两次。这样做是为了 (1) 确保对出错和段错误的插件具有高水平的抵抗力,以及 (2) 让操作系统在孙子进程退出后处理清理它。

【讨论】:

以上是关于为啥 fork() 两次[重复]的主要内容,如果未能解决你的问题,请参考以下文章

为啥字符串流的循环给我最后一个字两次[重复]

为啥相同的 CFLAGS 可以在 makefile 中重复两次?

为啥http请求从客户端发送两次到服务器(OPTION和POST)[重复]

为啥在调用 c++ fork 函数之前创建的值没有被父进程和子进程修改两次?

为啥 Eclipse 中会出现重复的方法建议?

为啥反斜杠出现两次?