为何要使用两次fork?

来源:互联网 发布:印度历史知乎 编辑:程序博客网 时间:2024/05/20 13:11

我们来看看什么事孤儿进程和僵尸进程:

孤儿进程:当子进程还没有结束的时候,其父进程退出,此时此进程就变成了孤儿进程,如图所示:
这里写图片描述
但是孤儿进程并不会存在很久,当系统发现孤儿进程时,init进程就收养孤儿进程,负责这个孤儿进程exit后的清理工作。

僵尸进程:当子进程在父进程退出前退出的情况下,子进程在从自己退出到父进程退出前的这一段时间是僵尸进程,父进程退出时候,会对这个僵尸进程做清理工作。
这里写图片描述

当父进程没有用wait回收子进程并不说明它不会回收子进程。子进程结束的时候会给其父进程发送一个SIGCHILD信号,父进程默认是忽略这个信号的,如果父进程通过signal函数设置了SIGCHILD的信号处理函数,则在信号处理函数中可以回收子进程的资源。事实上,即使父进程没有设置处理函数,子进程可以一直保持僵尸状态,当父进程结束后,init进程就会负责回收僵尸子进程。
但是,父进程是一个服务器进程,一直循环不退出,那么子进程一直保持这僵尸状态。虽然僵尸进程不会占用任何内存资源,但是过多的僵尸进程总是会影响系统性能的,这个时候怎么办呢?

两次fork法:
流程如图所示:
这里写图片描述

int main(int argc,char *argv[]){    //这是一个服务器进程    pid_t pid;    if((pid=fork())<0)  //服务器创建一个子进程    {        cout<<"error"<<endl;    }else if(pid==0)    {        if((pid=fork())<0)//服务器的子进程中再创建一个子进程        {            cout<<"error"<<endl;        }else if(pid>0)        {            cout<<"this is a child process of server"<<endl;            exit(0);//服务器的子进程退出        }else        {            cout<<"this is a child process of server's child"<<endl;            sleep(10);//为了保证exit退出的时间晚于其父进程            exit(0);//退出后,服务器的子进程已经退出,所以init进程将会接管这个僵尸进程        }    }else //pid>0,原始服务器进程    {        cout<<"this is server process"<<endl;    }}

如何避免僵尸进程?

1.通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
2.父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
3.如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

原创粉丝点击