socket06---僵尸进程的避免

来源:互联网 发布:微商城怎么查询数据库 编辑:程序博客网 时间:2024/05/16 12:55

在之前的篇章中,我们已经实现了点对点通信,服务器对多个客户端信息回射,我们一直都是以进程的方式来处理这些通信,一般都是server端在应对多个client端的请求时,fork出多个子进程来处理对应的client,这种方式带来了一种新的问题,叫僵尸进程

1.首先介绍一下什么是僵尸进程?

维基百科上的介绍如下:
在类UNIX系统中,僵尸进程是指完成执行(通过exit系统调用,或运行时发生致命错误或收到终止信号所致)但在操作系统的进程表中仍然有一个表项(进程控制块PCB),处于”终止状态”的进程。这发生于子进程需要保留表项以允许其父进程读取子进程的exit status:一旦退出态通过wait系统调用读取,僵尸进程条目就从进程表中删除,称之为”回收(reaped)”。正常情况下,进程直接被其父进程wait并由系统回收。进程长时间保持僵尸状态一般是错误的并导致资源泄漏。
英文术语zombie process源自en:zombie — 不死之人,隐喻子进程已死但仍然没有被收割。与正常进程不同,kill命令对僵尸进程无效。孤儿进程不同于僵尸进程,其父进程已经死掉,但孤儿进程仍能正常执行,但并不会变为僵尸进程,因为被init(进程ID号为1)收养并wait其退出。

简要地说,就是一个进程退出的时候并没有完全消失,而是留下一个称为僵尸进程(Zombie)的数据结构,里面存储着进程退出时的一些信息(供其他进程收集),之后一直在系统中存在,除非被主动回收或父进程退出,但是没任何作用

2.那么,我们这里是如何产生僵尸进程的呢?

开头就提到了,我们这里是server端对多个client端,并且是fork出子进程来应答。当我们的client通信完成了,在自己这一端退出了client,那么服务器那端对应的应答子进程也会收到一个结束符,宣告这个进程退出,但是由于是fork出来的子进程,父进程没有退出,所以子进程的退出势必会留下一个僵尸进程。

我们可以模拟看看僵尸进程的产生:
这里写图片描述
在client.c中修改了一下代码,设定了5个client端连接server,用其中一个通信,之后,我再ctrl+c终止了client的运行,这样一来5个client端全部退出。
之后我们再输入ps -ef|grep server,查看系统中和server有关的进程:
这里写图片描述
很明显,有5个server僵尸进程。后缀是defunct
这时候的server父进程还没有关闭,很明显就是它fork出来的5个子进程了。

这样就产生了僵尸进程,试想一下,正常的服务器每天要接受的请求和应答何其多,如果每个用户都留下这么一个僵尸进程,长期以往,服务器肯定会撑爆,所以这种情况必须主动处理。

3.处理僵尸进程—-信号量signal

子进程死后,系统会发送SIGCHLD 信号给父进程,父进程对其默认处理是忽略。如果想响应这个消息,父进程通常在SIGCHLD 信号事件处理程序中,使用wait系统调用来响应子进程的终止。

知道了这一点,我们就可直接在程序中添加代码了:

//直接在main函数中添加如下代码#include<signal.h>signal(SIGCHLD,SIG_IGN);//这个是交给系统init去回收僵尸进程,SIG_IGN表示忽略信号

因为是系统通知,这段代码可以写在父进程(main函数)的任意位置,都会捕捉到子进程返回的信号,并优先做出回应,优先执行这段代码。
添加完之后,在执行之前同样的操作,就不会出现僵尸进程了。

也可以如下这么做:

void handle_sigchld(int sig){    while(waitpid(-1,NULL,WNOHANG) > 0)        ;    //pid = -1  等待任何子进程,相当于 wait()    //WNOHANG   若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID}int main(void){    signal(SIGCHLD,handle_sigchld)    //使用waitpid函数实现,主动处理fork出来的会变成僵尸进程的方法    ......}

waitpid停止目前进程的执行,直到有信号到来或子进程结束。
waitpid执行成功,返回子进程识别码(PID),错误返回-1,存入全局变量errno中。

更多wait和waitpid函数的详解参考如下博客,这里不作解释了。

0 0
原创粉丝点击