为什么fork()两次

Nagios让我配置child_processes_fork_twice=

文件说

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

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

在Linux中,守护进程通常是通过在分叉孙子后退出中间进程两次来创建的。 这具有孤立孙子过程的效果。 因此,如果操作系统终止,它将成为操作系统的责任。 原因与所谓的僵尸进程有关,这些进程在退出后继续生存和消耗资源,因为他们的父母通常负责清理,也已经死亡。

好吧,现在首先:什么是僵尸进程?

这是一个已经死亡的过程,但是它的父母忙于做其他工作,因此无法收集孩子的退出状态。

在某些情况下,孩子跑了很长时间,父母不能等那么久,并且会继续工作(注意父母不会死,但继续其余的任务,但不关心孩子)。

通过这种方式,创建了一个僵尸进程。

现在让我们开始做生意吧。 如何在这里分叉两次?

需要注意的重要一点是,孙子完成了父进程希望其子进行的工作。

现在第一次调用fork时,第一个孩子只是再次分叉并退出。 这样,父母不必等待很长时间来收集孩子的退出状态(因为孩子唯一的工作就是创建另一个孩子并退出)。 所以,第一个孩子不会变成僵尸。

至于孙子,其父母已经去世了。 因此, init进程将采用孙进程,该进程始终收集其所有子进程的退出状态。 所以,现在父母不必等待很长时间,也不会创建僵尸进程。

还有其他方法可以避免僵尸进程; 这只是一种常见的技术。

希望这可以帮助!

另外从文档中 ,

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

Unix编程常见问题 §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()函数工作; 如果调用它们中的任何一个,它们将等待所有子进程终止,然后使用errno == ECHILD返回失败。

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

此代码演示了如何使用双fork方法来允许孙子进程被init采用,而没有僵尸进程的风险。

 #include  #include  #include  #include  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进程回收。