您的位置:首页 > 理论基础 > 计算机网络

linux网络多进程编程处理僵尸进程

2017-08-02 15:37 411 查看
前面的多进程程序中,当客户端关闭后,一个子进程会关闭,但是其pdb仍然存在,通过ps -ef|grep server可以查看到,对于服务器来说,大量的僵尸进程会消耗进程控制块数目,影响服务器性能,因此僵尸进程必须处理。

什么是僵尸进程?

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

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

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

僵尸进程的目的?

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

如何避免僵尸进程?

通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。

父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。

如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。

通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。

第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) do{perror(m);exit(EXIT_FAILURE);}while(1)
struct packet
{
int len;
char buf[1024];
};
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;
return -1;
}
else if (nread == 0)return count - nleft;
}
bufp = bufp + nread;
nleft = nleft - nread;
}
return count;
}
ssize_t writen(int fd, void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwrite;
char *bufp = (char*)buf;
while (nleft > 0)
{
if ((nwrite = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
{
continue;
return -1;
}
else if (nwrite == 0)continue;
}
bufp = bufp + nwrite;
nleft = nleft - nwrite;
}
return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret == -1 && errno == EINTR)continue;
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = (char*)buf;
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, bufp, nleft);
if (ret < 0)return ret;
else if (ret == 0)return ret;//对方关闭了套接口
nread = ret;
for (int i = 0; i < nread; i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd, bufp, i + 1);//将缓冲区数据移除
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 do_service(int connfd)
{
char recvbuf[1024];
int n;
while (1)
{
memset(&recvbuf, 0, sizeof(recvbuf));
int ret = readline(connfd, recvbuf, 1204);
if (ret == -1)
{
ERR_EXIT("read");
}
else if (ret == 0)
{
printf("client cloase\n");
break;
}
fputs(recvbuf, stdout);
writen(connfd, recvbuf, strlen(recvbuf));
}
}
void handle_sigchild(int sig)
{
//  wait(NULL);
while (waitpid(-1, NULL, WNOHANG) > 0);//回收所有子进程
}
int main(void)
{
//signal(SIGCHLD, SIG_IGN);
signal(SIGCHLD,handle_sigchild);
int listenfd;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)ERR_EXIT("socket");
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);
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)ERR_EXIT("setsockopt");
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < 0)ERR_EXIT("listten");
while (1)
{
int connfd;
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
if ((connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)ERR_EXIT("accept");
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid_t pid = fork();
if (pid == -1)ERR_EXIT("fork");
if (pid == 0)
{
close(listenfd);
do_service(connfd);
exit(EXIT_SUCCESS);
}
if (pid > 0)
{
close(connfd);
}
}
close(listenfd);
}


但是使用wait会出现问题,wait是阻塞的,当多个子进程同时退出时,wait并不能等待所有的子进程退出,当它等待第一个子进程退出后,就返回了,这个时候就要用waitpid方法,如果只是waitpid并不能解决同时退出的问题,要使用while(waitpid()),保证回收所有退出的子进程。


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: