1.僵死进程与孤儿进程
僵死进程:进程已经退出,但是没有回收内核 PCB 资源的进程叫僵死进程。
孤儿进程:父亲进程先于子进程退出后,这个子进程就是孤儿进程,父亲进程会被转移为 init(pid=1)进程。
僵死进程示例:
#include <stdio.h>
#include <stdlib.h>
#include "apue.h"
#include <sys/wait.h>
void test(int i , void *arg)
{
printf("helloworld,%d,%s\n",i,(char*)arg);
}
int main(int argc,char *argv[])
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork");
}
else if(pid == 0)
{
sleep(2);
printf("child pid:%d\n",getpid());
exit(10);
}
else
{
int status;
pid_t w_pid;
sleep(20);
w_pid = waitpid(-1,&status,0);
printf("w_pid:%d,status:%d\n",w_pid,status>>8);
}
exit(0);
}
父进程sleep 20秒时,子进程已经退出,但是没有回收PCB资源(进程PCB资源由父进程回收)。
#include <stdio.h>
#include <stdlib.h>
#include "apue.h"
#include <sys/wait.h>
void test(int i , void *arg)
{
printf("helloworld,%d,%s\n",i,(char*)arg);
}
int main(int argc,char *argv[])
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork");
}
else if(pid == 0)//子进程
{
printf("child pid:%d,parent pid:%d\n",getpid(),getppid());
sleep(4);
printf("child pid:%d,parent pid:%d\n",getpid(),getppid());
exit(10);
}
else//父进程
{
sleep(2);
exit(0);
}
return 0;
}
父进程比子进程
先退出了,子进程由pid==1的进程监管。
2.僵死进程
2.1僵死进程的原因及危害
一个进程在调用exit命令结束自己的生命的时候,其实他并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,他的作用是使进程退出,但是也仅仅限于一个正常的进程变成了一个僵尸进程,并不能完全将其销毁)。在linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态信息供其他进程收集,除此之外,僵尸进程不再占有任何存储空间。他需要他的父进程来为他收尸,如果他的父进程没有安装SIGCHLD信号处理函数调用wait 或 waitpid() 等待子进程结束,有没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时候父进程结束了,那么init进程会自动接手这个子进程,为他收尸,他还是能被清除掉的。但是如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是系统中为什么有时候会有很多的僵尸进程。
由此看出,僵死进程并不会佔用很多的资源。唯一的危害是影响了系统最大进程数,有可能造成后续进程无法分配资源。
2.2.僵死进程演示:
代码:
<span style="font-size:18px;">#include<stdio.h>
#include "apue.h"
#include "fcntl.h"
#include <sys/wait.h>
static void sig_int(int);
#define BUFFSIZE 4096
int glob = 6;
char buf[] = "a write to stdout\n";
const char cmd_init[]="ps -o pid,ppid,state,tty,command";
int main(int argc,char *argv[])
{
int var;
pid_t pid;
var = 88;
if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1)
err_sys("write error");
printf("before fork\n");
if((pid = fork())<0)
{
err_sys("fork error");
}
else if(pid == 0)
{
glob++;//子进程运行
var++;
}
else
{
sleep(60);//父进程运行
}
printf("pid = %d,glob = %d,var = %d\n",getpid(),glob,var);
exit(0);
}</span>
查看僵死进程:ps auwx | cut -c 10-15,45-50,63- | grep -i Z 查看僵尸进程pid、名称和进程路径。
僵尸进程的状态为Z,如下图的[first]
2.3.避免zombie的方法
1)在svr4中,如果调用signal或是sigset将SIGCHLD的配置设置为忽略,则不会产生僵死子进程。另外,使用svr4版本sigaction,则可设置SA_NOCLDWAIT标志以避免子进程僵死。Linux中也可以使用这个,在一个程序开始调用这个函数signal(SIGCHLD,SIG_IGN);
2)调用fork两次:第一次调用fork创建子进程,第二次调用创建孙子进程,当子进程退出后,不管父进程是否在循环运行,孙子进程都被init进程接手了,不会产生僵死进程。示例代码:
<span style="font-size:18px;">#include<stdio.h>
#include "apue.h"
#include "fcntl.h"
#include <sys/wait.h>
#define BUFFSIZE 4096
int main(int argc,char *argv[])
{
pid_t pid;
if((pid = fork())<0)
err_sys("fork error");
else if(pid == 0)//第一个子进程创建
{
if((pid = fork())<0)//第二个子进程创建
err_sys("fork error");
else if(pid > 0)
exit(0);//第一个子进程退出,第二个子进程被init进程托管,不会成为僵死进程
//第二个子进程运行
sleep(2);
printf("second child,parent pid = %d\n",getppid());
exit(0);
}
if(waitpid(pid,NULL,0)!=pid)//父进程:等待第一个子进程退出
err_sys("fork error");
exit(0);
}</span>
3)用waitpid等待子进程返回。
3.孤儿进程