孤儿进程和僵尸进程
前几天接到某互联网公司的电话面试,面试官问我两次fork()的作用,我一头雾水,说不知道。知识面还是太窄了。下面就总结下两次fork()的作用。
首先,要了解什么叫僵尸进程,什么叫孤儿进程,以及服务器进程运行所需要的一些条件。两次fork()就是为了解决这些相关的问题而出现的一种编程方法。
孤儿进程
孤儿进程是指父进程在子进程结束之前死亡(return 或exit)。如下图1所示:
图1 孤儿进程
但是孤儿进程并不会像上面画的那样持续很长时间,当系统发现孤儿进程时,init进程就收养孤儿进程,成为它的父亲,child进程exit后的资源回收就都由init进程来完成。
僵尸进程
僵尸进程是指子进程在父进程之前结束了,但是父进程没有用wait或waitpid回收子进程。如下图所示:
图2 僵尸进程
父进程没有用wait回收子进程并不说明它不会回收子进程。子进程在结束的时候会给其父进程发送一个SIGCHILD信号,父进程默认是忽略SIGCHILD信号的,如果父进程通过signal()函数设置了SIGCHILD的信号处理函数,则在信号处理函数中可以回收子进程的资源。
事实上,即便是父进程没有设置SIGCHILD的信号处理函数,也没有关系,因为在父进程结束之前,子进程可以一直保持僵尸状态,当父进程结束后,init进程就会负责回收僵尸子进程。
但是,如果父进程是一个服务器进程,一直循环着不退出,那子进程就会一直保持着僵尸状态。虽然僵尸进程不会占用任何内存资源,但是过多的僵尸进程总还是会影响系统性能的。黔驴技穷的情况下,该怎么办呢?
这个时候就需要一个英雄来拯救整个世界,它就是两次fork()技法。
两次fork()技法
两次fork()的流程如下所示:
图3 两次fork的控制流
如上图3所示,为了避免子进程child成为僵尸进程,我们可以人为地创建一个子进程child1,再让child1成为工作子进程child2的父进程,child2出生后child1退出,这个时候child2相当于是child1产生的孤儿进程,这个孤儿进程由系统进程init回收。这样,当child2退出的时候,init就会回收child2的资源,child2就不会成为孤魂野鬼祸国殃民了。
<unix环境高级编程>这本书里提供了两次fork的一个例子,代码如下:
- int main(void)
- {
- pid_t pid;
- if ( (pid = fork()) < 0)
- err_sys("fork error");
- else if (pid == 0)
- { /* first child */
- if ( (pid = fork()) < 0)
- err_sys("fork error");
- else if (pid > 0)
- exit(0); /* parent from second fork == first child */
- /* We're the second child; our parent becomes init as soon
- as our real parent calls exit() in the statement above.
- Here's where we'd continue executing, knowing that when
- we're done, init will reap our status. */
- sleep(2);
- printf("second child, parent pid = %d\n", getppid());
- exit(0);
- }
- if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
- err_sys("waitpid error");
- /* We're the parent (the original process); we continue executing,
- knowing that we're not the parent of the second child. */
- exit(0);
- }
- 理所当然,第二个子进程的父进程是进程号为1的init进程。
一言以蔽之,两次fork()是人为地创建一个工作子进程的父进程,然后让这个人为父进程退出,之后工作子进程就由init回收,避免了工作子进程成为僵尸进程。