什么是僵尸进程?

首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。

而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。

任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。

僵尸进程的目的?

设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)。


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


  如果服务器端因为客户端的关闭,循环开启的处理子进程会成为僵尸进程。当有5个子进程退出,就会有5个SIGCHLD  信号发给父进程。当第一个信号过来,父进程处于sighandler过程,在此过程中,若有其他信号过来,信号(不可靠信号)会丢失,只排队一个。所以最终只会处理两个SIGCHLD信号,剩3个僵尸进程;若是5个一起来,可能只处理第一个(剩4个僵尸进程)。用while解决

/*
服务端每次会fork一个子进程处理客户单请求,每个客户端断开连接,会有一个子进程成为僵尸进程(父进程不处理的话)
*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<sys/wait.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nleft=count;
ssize_t nread;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR)
continue;
else
return -1;
}
else if(nread==0)
return (count-nleft);
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<=0)
{
if(errno==EINTR)
continue;
return -1;
}else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count; }
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
while(1)
{
int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
if(ret==-1&&errno==EINTR)
continue;
return ret;
}
}
//偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void * buf,size_t maxline)
{
int ret;
int nread;
size_t nleft=maxline;
char *bufp=(char*)buf;
while(1)
{
ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
if(ret<0)
return ret;
else if(ret==0)
return ret;
nread=ret;
int i;
for(i=0;i<nread;i++)
{
if(bufp[i]=='\n')
{
ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
if(ret!=i+1)
exit(EXIT_FAILURE);
return ret;
}
}
if(nread>nleft)
exit(EXIT_FAILURE);
nleft-=nread;
ret=readn(sockfd,bufp,nread);
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//移动指针继续窥看
}
return -1;
}
void echo_srv(int conn)
{
int ret;
char recvbuf[1024];
while(1)
{
memset(&recvbuf,0,sizeof(recvbuf));
//使用readn之后客户端发送的数据不足1024会阻塞
//在客户端程序中确定消息的边界,发送定长包
ret=readline(conn,recvbuf,1024);
//客户端关闭
if(ret==-1)
ERR_EXIT("readline");
else if(ret==0)
{
printf("client close\n");
break;//不用继续循环等待客户端数据
}
fputs(recvbuf,stdout);
writen(conn,recvbuf,strlen(recvbuf));
}
}
void handle_sigchld(int sig)
{
//法一 :只能等待第一个,不能等待所有的子进程,通常剩余3个僵尸进程。剩余3个说明不可靠信号排队一个
//wait(NULL);
//当没有子进程退出,返回-1
while(waitpid(-1,NULL, WNOHANG)>0)
;//pid==-1等待所有子进程结束。等到一个子进程返回值大于0
}
int main(void)
{
//方法一:
//signal(SIGCHLD,SIG_IGN);//父进程忽略子进程终止信号,解决僵尸进程
/*方法二 */
signal(SIGCHLD,handle_sigchld);
int listenfd;
if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
ERR_EXIT("socket error");
//if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) //本地协议地址赋给一个套接字
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址 //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
ERR_EXIT("setsockopt error");
//绑定本地套接字
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind error");
if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
ERR_EXIT("listen error"); struct sockaddr_in peeraddr;//对方套接字地址
socklen_t peerlen=sizeof(peeraddr);
int conn;//已连接套接字(主动套接字)
pid_t pid;
while(1){
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept error");
//连接好之后就构成连接,端口是客户端的。peeraddr是对端
printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0){
close(listenfd);
echo_srv(conn);
//某个客户端关闭,结束该子进程,否则子进程也去接受连接
//虽然结束了exit退出,但是内核还保留了其信息,父进程并未为其收尸。
exit(EXIT_SUCCESS);
}else close(conn);
}
return 0;
}

//客户端:建立5个连接请求服务端,再关闭。

//五个客户端去连接并发服务器
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nleft=count;
ssize_t nread;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR)
continue;
else
return -1;
}
else if(nread==0)
return (count-nleft);
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<=0)
{
if(errno==EINTR)
continue;
return -1;
}else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count; }
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
while(1)
{
int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
if(ret==-1&&errno==EINTR)
continue;
return ret;
}
}
//偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void * buf,size_t maxline)
{
int ret;
int nread;
size_t nleft=maxline;
char *bufp=(char*)buf;
while(1)
{
ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
if(ret<0)
return ret;
else if(ret==0)
return ret;
nread=ret;
int i;
for(i=0;i<nread;i++)
{
if(bufp[i]=='\n')
{
ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
if(ret!=i+1)
exit(EXIT_FAILURE);
return ret;
}
}
if(nread>nleft)
exit(EXIT_FAILURE);
nleft-=nread;
ret=readn(sockfd,bufp,nread);
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//移动指针继续窥看
}
return -1;
}
void echo_cli(int sock)
{
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)//默认有换行符
{
writen(sock,sendbuf,strlen(sendbuf));
int ret=readline(sock,recvbuf,1024);
if(ret==-1)
ERR_EXIT("readline");
else if(ret==0)
{
printf("service closed\n");
break;
}
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
}
int main(void)
{
int sock[5];//客户端创建5个套接字
int i=0;
for(i=0;i<5;i++)
{
if((sock[i]=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
ERR_EXIT("socket error"); struct sockaddr_in servaddr;//本地协议地址赋给一个套接字
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器段地址
//inet_aton("127.0.0.1",&servaddr.sin_addr); if(connect(sock[i],(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect"); //利用getsockname获取客户端本身地址和端口,即为对方accept中的对方套接口
struct sockaddr_in localaddr;
socklen_t addrlen=sizeof(localaddr);
if(getsockname(sock[i],(struct sockaddr *)&localaddr,&addrlen)<0)
ERR_EXIT("getsockname error");
printf("local IP=%s, local port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
//使用getpeername获取对方地址 }
echo_cli(sock[0]);//选择一个与服务器通信
return 0;
}

僵尸进程与SIGCHLD信号的更多相关文章

  1. linux下的僵尸进程处理SIGCHLD信号

    什么是僵尸进程? 首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息.这些信息至少包括进程ID,进程的终止状态,以及该 ...

  2. [转] linux下的僵尸进程处理SIGCHLD信号

    什么是僵尸进程? 首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息.这些 信息至少包括进程ID,进程的终止状态,以及 ...

  3. linux下的僵尸进程处理SIGCHLD信号【转】

    转自:http://www.cnblogs.com/wuchanming/p/4020463.html 什么是僵尸进程? 首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打 ...

  4. 僵进程与SIGCHLD信号

    参考: https://www.cnblogs.com/webor2006/p/4014586.html wait()和waitpid()的参数解析:https://blog.csdn.net/csd ...

  5. python练习笔记——利用信号signal处理僵尸进程

    1 signal处理僵尸进程的基于语法 利用信号signal处理僵尸进程的方法:signal(SIGCHLD,SIG_IGN),该方法也是第三种处理僵尸进程的方法. SIGCHLD:子进程状态改变后产 ...

  6. Linux 系统中僵尸进程

    Linux 系统中僵尸进程和现实中僵尸(虽然我也没见过)类似,虽然已经死了,但是由于没人给它们收尸,还能四处走动.僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸.配图源 ...

  7. Linux 网络编程详解六(多进程服务器僵尸进程解决方案)

    小结:在点对点p2p程序中,服务器端子程序退出,子进程会主动发送信号,关闭父进程,但是这种模式导致服务器只能支持一个客户端连接,本章节中使用新的框架,子进程退出,不主动发送信号关闭父进程,而是父进程安 ...

  8. wait、waitpid 僵尸进程 孤儿进程

    man wait: NAME wait, waitpid, waitid - wait for process to change state SYNOPSIS #include <sys/ty ...

  9. Unix/Linux僵尸进程

    1. 僵尸进程的产生: 一个进程调用exit命令结束自己生命的时候,其实它并没有真正的被销毁,而是留下一个称为“僵尸进程”的数据结构.这时它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度 ...

随机推荐

  1. MeteoInfoLab脚本示例:获取一维数据并绘图

    气象数据基本为多维数据(通常是4维,空间3维加时间维),只让数据中一维可变,其它维均固定即可提取一维数据.比如此例中固定了时间维.高度维.纬度维,只保留经度维可变:hgt = f['hgt'][0,[ ...

  2. Get提交方式中文乱码

    Get提交方式中文乱码 今天在servlet使用中,在Get方法中获取提交的中文参数,发现是乱码,我用的是Tomcat7. 在Tomcat9中: get方式的参数是放在请求头中,而Tomcat9对请求 ...

  3. Linux给特定进程单独指定DNS

    Linux本身只能通过/etc/resolv.conf设置全系统的DNS.这里有一种给特定进程单独设置DNS的方法,通过免root的mount namespace达成.使用脚本只需要一条简洁的命令就可 ...

  4. spring boot: 用thymeleaf嵌套循环展示多层数据(spring boot 2.3.2)

    一,什么情况下会用到嵌套循环? 当我们展示多个分类时,每个分类下又展示出推荐的前几个商品,   这时我们需要用到嵌套循环 看一个例子: 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https:/ ...

  5. centos8平台安装ansible2.9

    一,ansible的用途: ansible是基于python开发的自动化运维工具, 它基于SSH远程连接服务, 可以实现批量系统配置.批量软件部署.批量文件拷贝.批量运行命令等多个运维功能 因为基于s ...

  6. shell变量替换 SHELL字符串处理技巧(${}、##、%%)

      在SHELL编程中,经常要处理一些字符串变量.比如,计算长度啊.截取子串啊.字符替换啊等等,常常要用到awk.expr.sed.tr等命令.下面给大家介绍个简单的字符串处理方法,用不着嵌套复杂的子 ...

  7. HTML <big> 标签

    HTML <big> 标签 什么是<big> 标签? <big> 标签呈现大号字体效果. 使用 <big> 标签可以很容易地放大字体.这简直不能再简单了 ...

  8. 【git冲突解决】: Please commit your changes or stash them before you merge.

    刚刚使用 git pull 命令拉取代码时候,遇到了这样的问题: error: Your local changes to the following files would be overwritt ...

  9. Java 第三课 数组排序

    1.java.util.Arrays.sort(arr)  //升序 2.冒泡排序:相邻元素比较 for (int i=0; i <arr.length-1; i++){//内部遍历一次,确定最 ...

  10. MFiX-DEM中的并行碰撞搜索

    基于MFiX-19.2.2 DEM并行程序中的颗粒循环 在DEM并行程序中,每个进程只循环该进程包含的颗粒,并且每个进程还有一层ghost cell,用来存放另一个进程发送过来的颗粒信息. 下面添加一 ...