linux僵尸进程产生的原因以及如何避免产生僵尸进程
给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取。这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(init进程将wait它们,从而去除僵尸状态)。
但通常情况下,我们是不愿意留存僵尸进程的,它们占用内核中的空间,最终可能导致我们耗尽进程资源。那么为什么会产生僵尸进程以及如何避免产生僵尸进程呢?下边我将从这两个方面进行分析。
僵尸进程的原因
我们知道,要在当前进程中生成一个子进程,一般需要调用fork这个系统调用,fork这个函数的特别之处在于一次调用,两次返回,一次返回到父进程中,一次返回到子进程中,我们可以通过返回值来判断其返回点:
pid_t child = fork();
if( child < ) { //fork error.
perror("fork process fail.\n");
} else if( child == ) { // in child process
printf(" fork succ, this run in child process\n ");
} else { // in parent process
printf(" this run in parent process\n ");
}
如果子进程先于父进程退出, 同时父进程又没有调用wait/waitpid,则该子进程将成为僵尸进程。通过ps命令,我们可以看到该进程的状态为Z(表示僵死),如图1所示:

(图1)
备注: 有些unix系统在ps命令输出的COMMAND栏以<defunct>指明僵尸进程。
代码如下:
if( child == - ) { //error
perror("\nfork child error.");
exit();
} else if(child == ){
cout << "\nIm in child process:" << getpid() << endl;
exit();
} else {
cout << "\nIm in parent process." << endl;
sleep();
}
让父进程休眠600s, 然后子进程先退出,我们就可以看到先退出的子进程成为僵尸进程了(进程状态为Z)
避免产生僵尸进程
我们知道了僵尸进程产生的原因,下边我们看看如何避免产生僵尸进程。
一般,为了防止产生僵尸进程,在fork子进程之后我们都要wait它们;同时,当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,所以我们可以建立一个捕获SIGCHLD信号的信号处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的。如下代码所示:
void sig_chld( int signo ) {
pid_t pid;
int stat;
pid = wait(&stat);
printf( "child %d exit\n", pid );
return;
}
int main() {
signal(SIGCHLD, &sig_chld);
}
现在main函数中给SIGCHLD信号注册一个信号处理函数(sig_chld),然后在子进程退出的时候,内核递交一个SIGCHLD的时候就会被主进程捕获而进入信号处理函数sig_chld,然后再在sig_chld中调用wait,就可以清理退出的子进程。这样退出的子进程就不会成为僵尸进程。
然后,即便我们捕获SIGCHLD信号并且调用wait来清理退出的进程,仍然不能彻底避免产生僵尸进程;我们来看一种特殊的情况:
我们假设有一个client/server的程序,对于每一个连接过来的client,server都启动一个新的进程去处理来自这个client的请求。然后我们有一个client进程,在这个进程内,发起了多个到server的请求(假设5个),则server会fork 5个子进程来读取client输入并处理(同时,当客户端关闭套接字的时候,每个子进程都退出);当我们终止这个client进程的时候 ,内核将自动关闭所有由这个client进程打开的套接字,那么由这个client进程发起的5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个。server端接受到这5个FIN的时候,5个子进程基本在同一时刻终止。这就又导致差不多在同一时刻递交5个SIGCHLD信号给父进程,如图2所示:

(图2)
正是这种同一信号多个实例的递交造成了我们即将查看的问题。
我们首先运行服务器程序,然后运行客户端程序,运用ps命令看以看到服务器fork了5个子进程,如图3:

(图3)
然后我们Ctrl+C终止客户端进程,在我机器上边测试,可以看到信号处理函数运行了3次,还剩下2个僵尸进程,如图4:

(图4)
通过上边这个实验我们可以看出,建立信号处理函数并在其中调用wait并不足以防止出现僵尸进程,其原因在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为Unix信号一般是不排队的(我的这篇博客中有提到http://www.cnblogs.com/yuxingfirst/p/3160697.html)。 更为严重的是,本问题是不确定的,依赖于客户FIN到达服务器主机的时机,信号处理函数执行的次数并不确定。
正确的解决办法是调用waitpid而不是wait,这个办法的方法为:信号处理函数中,在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,他告知waitpid在有尚未终止的子进程在运行时不要阻塞。(我们不能在循环内调用wait,因为没有办法防止wait在尚有未终止的子进程在运行时阻塞,wait将会阻塞到现有的子进程中第一个终止为止),下边的程序分别给出了这两种处理办法(func_wait, func_waitpid)。
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <error.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h> typedef void sigfunc(int); void func_wait(int signo) {
pid_t pid;
int stat;
pid = wait(&stat);
printf( "child %d exit\n", pid );
return;
}
void func_waitpid(int signo) {
pid_t pid;
int stat;
while( (pid = waitpid(-, &stat, WNOHANG)) > ) {
printf( "child %d exit\n", pid );
}
return;
} sigfunc* signal( int signo, sigfunc *func ) {
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = ;
if ( signo == SIGALRM ) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 4.4BSD */
#endif
}
if ( sigaction(signo, &act, &oact) < ) {
return SIG_ERR;
}
return oact.sa_handler;
} void str_echo( int cfd ) {
ssize_t n;
char buf[];
again:
memset(buf, , sizeof(buf));
while( (n = read(cfd, buf, )) > ) {
write(cfd, buf, n);
}
if( n < && errno == EINTR ) {
goto again;
} else {
printf("str_echo: read error\n");
}
} int main() { signal(SIGCHLD, &func_waitpid); int s, c;
pid_t child;
if( (s = socket(AF_INET, SOCK_STREAM, )) < ) {
int e = errno;
perror("create socket fail.\n");
exit();
} struct sockaddr_in server_addr, child_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons();
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); if( bind(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < ) {
int e = errno;
perror("bind address fail.\n");
exit();
} if( listen(s, ) < ) {
int e = errno;
perror("listen fail.\n");
exit();
}
while() {
socklen_t chilen = sizeof(child_addr);
if ( (c = accept(s, (struct sockaddr *)&child_addr, &chilen)) < ) {
perror("listen fail.");
exit();
} if( (child = fork()) == ) {
close(s);
str_echo(c);
exit();
}
close(c);
}
} //client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <error.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h> void str_cli(FILE *fp, int sfd ) {
char sendline[], recvline[];
memset(recvline, , sizeof(sendline));
memset(sendline, , sizeof(recvline));
while( fgets(sendline, , fp) != NULL ) {
write(sfd, sendline, strlen(sendline));
if( read(sfd, recvline, ) == ) {
printf("server term prematurely.\n");
}
fputs(recvline, stdout);
memset(recvline, , sizeof(sendline));
memset(sendline, , sizeof(recvline));
}
} int main() {
int s[];
for (int i=; i<; i++) {
if( (s[i] = socket(AF_INET, SOCK_STREAM, )) < ) {
int e = errno;
perror("create socket fail.\n");
exit();
}
} for (int i=; i<; i++) {
struct sockaddr_in server_addr, child_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons();
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
if( connect(s[i], (struct sockaddr *)&server_addr, sizeof(server_addr)) < ) {
perror("connect fail.");
exit();
}
} sleep();
str_cli(stdin, s[]);
exit();
}
linux僵尸进程产生的原因以及如何避免产生僵尸进程的更多相关文章
- linux 中的进程wait()和waitpid函数,僵尸进程详解,以及利用这两个函数解决进程同步问题
转载自:http://blog.sina.com.cn/s/blog_7776b9d3010144f9.html 在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / wait ...
- Linux下java nohup 后台运行关闭后进程停止的原因,不挂断后台运行命令
Linux下java nohup 后台运行关闭后进程停止的原因,不挂断后台运行命令 今天写sh脚本发现一终止命令程序就停止运行了,检查了很久才发现后面少了个&字符导致的!错误写法:nohup ...
- Linux CFS调度器之pick_next_task_fair选择下一个被调度的进程--Linux进程的管理与调度(二十八)
1. CFS如何选择最合适的进程 每个调度器类sched_class都必须提供一个pick_next_task函数用以在就绪队列中选择一个最优的进程来等待调度, 而我们的CFS调度器类中, 选择下一个 ...
- .NET Core项目部署到Linux(Centos7)(八)为.NET Core项目创建Supervisor进程守护监控
目录 1.前言 2.环境和软件的准备 3.创建.NET Core API项目 4.VMware Workstation虚拟机及Centos 7安装 5.Centos 7安装.NET Core环境 6. ...
- Linux下进程线程,Nignx与php-fpm的进程线程方式
1.进程与线程区别 进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集.从内核的观点看,进程的目的就是担当分配系统资源(CPU时间.内存等)的基本单位. 线程是进程的一个执行流, ...
- 《Linux内核设计与实现》读书笔记 第三章 进程管理
第三章进程管理 进程是Unix操作系统抽象概念中最基本的一种.我们拥有操作系统就是为了运行用户程序,因此,进程管理就是所有操作系统的心脏所在. 3.1进程 概念: 进程:处于执行期的程序.但不仅局限于 ...
- Linux内核分析之理解进程调度时机跟踪分析进程调度与进程切换的过程
一.原理分析 1.调度时机 背景不同类型的进程有不同的调度需求第一种分类I/O-bond:频繁的进行I/O:通常会花费很多时间等待I/O操作的完成CPU-bound:计算密集型:需要大量的CPU时间进 ...
- 在windows和linux下如何查看80端口占用情况?是被哪个进程占用?如何终止等
一.在windows下如何查看80端口占用情况?是被哪个进程占用?如何终止等 这里主要是用到windows下的DOS工具,点击"开始"--"运行",输入&quo ...
- linux 下 取进程占用内存(MEM)最高的前10个进程
# linux 下 取进程占用 cpu 最高的前10个进程ps aux|head -1;ps aux|grep -v PID|sort -rn -k +3|head # linux 下 取进程占用内存 ...
随机推荐
- dotnet core的下载地址 以及sdk和runtime的 version 简单说明
1. dotnet core 2.1 的下载地址 https://dotnet.microsoft.com/download/dotnet-core/2.1 2. dotnet core 2.2 的下 ...
- React learn path
React learn path The Road to learn React https://github.com/the-road-to-learn-react https://roadtore ...
- jmeter 参数化测试
变量的值可以不改变,可以被定义一次,如果不使用,不会改变值. 在测试计划中,变量可以做为方便的频繁使用的表达式,或都作为在测试运行中的可能会改变的常量,例如 线程组中线程的数量.主机名称 在确定一个测 ...
- face detection,landmark, recognition with deeplearning
人脸特征点定位 Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks c ...
- C++拷贝构造函数与 = 重载
调用拷贝构造函数进行初始化的时候,是不会调用=重载的. // test.cpp : 定义控制台应用程序的入口点. // //#include "stdafx.h" #include ...
- python 模块之-configparser
python 模块configparser 配置文件模块 import configparser config = configparser.ConfigParser() config[&q ...
- Codeforces Round #424 (Div. 2, rated, based on VK Cup Finals)A,B,C
A:链接:http://codeforces.com/contest/831/problem/A 解题思路: 从前往后分别统计递增,相等,递减序列的长度,如果最后长度和原序列长度相等那么就输出yes: ...
- Linux Install geoip
安装方法 http://php.net/manual/en/geoip.installation.phpgeoip中的PHP函数介绍:http://php.net/manual/en/book.geo ...
- 自学Linux Shell9.3-基于Red Hat系统工具包:RPM属性依赖的解决方式-YUM在线升级
点击返回 自学Linux命令行与Shell脚本之路 9.3-基于Red Hat系统工具包:RPM属性依赖的解决方式-YUM在线升级 本节主要介绍基于Red Had的系统(测试系统centos) yum ...
- How to Add Trust Sites into IE before IE10 through Group Policy
Due to IE10 published, I'll conclude the methods that how to add trust sites in to IE of the version ...