linux下出现大量僵尸进程,Linux下僵尸进程的产生和解决方法

僵尸进程的产生

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

客户端代码

#include

#include

#include

#include

#include

#include

#include

#include

#include

#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

#include

#include

#include

#include

#include

#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;

}

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

dbee88d29e07b5cb7f7abc27dd728a43.png

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

1dd8153d155bdec768f89881747bd20b.png

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

e666de3b613ceacb7e798333acfb5e29.png

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

2bb465c546346aaff41cb630ca66e34d.png

僵尸进程产生的原因是,服务器子进程终止时,会给父进程发送一个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

#include

#include

#include

#include

#include

#include

#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);

}

}

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

3fd8865c1532310c9d8489c4844ed61e.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值