您的位置:首页 > 运维架构 > Linux

Linux下僵尸进程的产生和解决方法

2018-03-15 23:00 931 查看

僵尸进程的产生

由下面一段cs架构的代码说明下,僵尸进程的产生,下面是一个简单的回射服务器,客户端负责从标准输入读入数据,写到服务端,服务端主进程监听连接套接字,fork一个子进程处理连接套接字,读入数据和回写给客户端。大写的函数只对出错的情况进行处理。

客户端代码

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "socketio.h"

int main()
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(5566);
Connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

char sendline[1024], recvline[1024];
memset(sendline, 0, sizeof(sendline));
memset(recvline, 0, sizeof(recvline));

while (fgets(sendline, sizeof(sendline), stdin) != NULL)
{
int nread = 0;
int nwrite = 0;
nwrite = write(sockfd, sendline, strlen(sendline));
if(nwrite < 0)
{
if (errno == EINTR)
continue;
perror("write");
}
nread = read(sockfd, recvline, sizeof(recvline));
if(nread < 0)
{
if (errno == EINTR)
continue;
perror("read");
}
if (nread == 0)
{
printf("server close\n");
// break;
}

fputs(recvline, stdout);
memset(sendline, 0, sizeof(sendline));
memset(recvline, 0, sizeof(recvline));

}
close(sockfd);
return 0;
}


服务端代码

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <stdio.h>
#include "socketio.h"

int main()
{
struct sockaddr_in servaddr, cliaddr;
socklen_t clilen;
pid_t pid;
int listenfd, connfd;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(5566);

int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
Bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
Listen(listenfd, 5);

while(1)
{
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
if ((pid = fork()) == 0)
{
while(1)
{
int nread = 0;
int nwrite = 0;
char readbuf[1024];
memset(readbuf, 0, sizeof(readbuf));
close(listenfd);
// nread = read(connfd, readbuf, sizeof(readbuf));

while((nread = read(connfd, readbuf, sizeof(readbuf))) < 0)
{
if (errno == EINTR)
continue;
perror("read");
}
if (nread == 0)
{
printf("client close\n");
exit(0);
}
printf("nread: %d\n", nread);
fputs(readbuf, stdout);
nwrite = write(connfd, readbuf, strlen(readbuf));
while(nwrite < 0)
{
if (errno == EINTR)
continue;
perror("write");
}
}
}
else if (pid < 0)
{
perror("fork");
exit(-1);
}
close(connfd);
}
return 0;
}


第一次连接通信时,我们可以看到,并没有产生僵尸进程。



当客户端结束通信,服务端主进程不退出,服务端子进程输出client close并exit(0)退出时,可以看到僵尸进程产生了。



服务端一直没有退出,当客户端发起第二次连接通信时,我们可以看到,之前产生的进程僵尸进程依然残留。



客户端第二次结束通信,又产生了一个僵尸进程。



僵尸进程产生的原因是,服务器子进程终止时,会给父进程发送一个SIGCHLD信号,此时父进程的默认的处理是忽略此信号,所以产生了僵尸进程,只有等到服务端完全关闭时,才会自动的回收僵尸进程,但是服务端通常是长时间的工作,如果每处理一个连接就会留下一个僵尸进程的话将会耗费大量资源,所以我们需要捕获子进程终止时的信号,让系统杀死僵尸进程。

此时我们引入一个waitpid函数

pid_t waitpid(pid_t pid, int* statloc, int options);
成功时返回进程pid(大于零)


此函数用于处理僵尸进程。

还需要一个捕获信号的函数,表明当捕获到SIGCHLD类型的信号时调用sig_chld函数。

signal(SIGCHLD, sig_chld);


信号处理函数我们需要在循环地调用waitpid,原因是防止在进入信号处理函数的时候收到另外多个SIGCHLD信号,然而只被捕获一次或两次信号,从而只能处理1-2次信号。WNOHANG参数可以避免有尚未终止子进程运行时阻塞,这也是这里不用wait的原因。

void sig_chld()
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
}


最终服务端的代码

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include "socketio.h"

void sig_chld() { pid_t pid; int stat; while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated\n", pid); }

int main()
{
struct sockaddr_in servaddr, cliaddr;
socklen_t clilen;
pid_t pid;
int listenfd, connfd;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(5566);

int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
Bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
Listen(listenfd, 5);
signal(SIGCHLD, sig_chld);

while(1)
{
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
if ((pid = fork()) == 0)
{
while(1)
{
int nread = 0;
int nwrite = 0;
char readbuf[1024];
memset(readbuf, 0, sizeof(readbuf));
close(listenfd);
// nread = read(connfd, readbuf, sizeof(readbuf));

while((nread = read(connfd, readbuf, sizeof(readbuf))) < 0)
{
if (errno == EINTR)
continue;
perror("read");
}
if (nread == 0)
{
printf("client close\n");
exit(0);
}

fputs(readbuf, stdout);
nwrite = write(connfd, readbuf, strlen(readbuf));
while(nwrite < 0)
{
if (errno == EINTR)
continue;
perror("write");
}
}
}
else if (pid < 0)
{
perror("fork");
exit(-1);
}
close(connfd);
}
}


最终结果,客户端结束通信,服务端子进程结束,主进程仍然在运行时,不产生僵尸进程。

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