为什么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
进程回收。