目录
僵尸进程的概念及危害
僵尸进程:子进程先于父进程退出,但是系统回收进程资源时,子进程PCB残留,这个进程为僵尸进程
危害:1.PCB残留导致内存泄露
2.由于PCB创建的数量是固定的,一个僵尸进程会占用一个PCB,从而影响新进程的创建
为什么PCB会有残留呢?
我们假设张三的孩子张思睿出了车祸,医院没救过来,结果医院直接一把火给尸体烧了,你说这合理吗?这显然不合理,只有父母有权来决定自己的孩子尸体怎么处理,父母可以通过别的方式来知道自己的孩子是怎么死的,同理,父进程可以通过查看子进程残留的PCB来知道子进程是怎么结束的,这也就是子进程残留PCB的原因
怎么解决僵尸进程这一问题呢?
这里就要用到wait函数和waitpid函数,僵尸进程产生后,只有父进程可以进行回收处理
父进程回收子进程的PCB,可以处理内存泄露,还可以对子进程的退出原因进行校验,通过PCB校验,掌握子进程的退出原因
wait函数
功能:父进程可以调用该函数来清除僵尸进程
pid_t zpid = wait(int* status);//僵尸的英文是zombie,所以这个地方变量名设置为zpid
这是经典的阻塞回收函数,如果有子进程存在,那么就一直阻塞等待子进程死亡,然后回收
头文件 #include<sys/wait.h>
status(输出参数)就是状态,父进程可以通过status来知晓子进程退出的原因,并回收释放PCB
如果传入的是NULL,就只回收释放PCB,不传出子进程退出原因
返回值:
成功就返回僵尸进程的pid(>0),失败就返回-1(没有子进程)
以下是wait函数清除僵尸进程的代码实现
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>//wait函数的头文件
int main(void)
{
pid_t pid;
pid = fork();
if (pid > 0)
{
pid_t zpid;
printf("parent %d running\n...\n", getpid());
if ((zpid = wait(NULL)) > 0)//成功的话wait函数的返回值大于0
//回收成功,返回僵尸进程的zpid,NULL-->不返回子进程退出原因
{
printf("parent wait success zombie pid %d\n", getpid());
}
while (1)
{
sleep(1);
}
}
else if (pid == 0)
{
printf("Child %d Running\n...\n", getpid());
sleep(10);
exit(0);
//这个地方,子进程退出,PCB残留,产生僵尸进程,进入判断if ((zpid = wait(NULL)) > 0)
}
else
{
printf("fork call failed\n");
exit(0);
}
return 0;
}
waitpid函数
pid_t zpid = waitpid(pid_t pid , int* status , int opt);
支持非阻塞回收,如果当前子进程无法回收则返回
头文件:#include<sys/wait.h>
Pram:
pid 回收方式 -1 回收任意子进程 >0 指定一个子进程的pid,然后回收该子进程
0 可以回收与调用进程(父进程)同组的所有子进程(同组回收)
< -1 可以回收与调用进程(父进程)同组的所有子进程(同组回收)
Status 校验子进程的退出原因,传入NULL则不校验
Opt 设置waitpid的工作模式,传入参数WNOHANG,则将waitpid改为非阻塞工作模式
返回值:
回收成功返回僵尸进程的zpid
回收失败返回 -1(表示没有子进程)
非阻塞返回0(表示当前子进程不可回收,立即返回)
以下是waitpid函数清除僵尸进程的代码实现
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid > 0)
{
pid_t zpid;
while ((zpid = waitpid(-1, NULL, WNOHANG)) != -1)
//因为我们传入了WNOHANG,waitpid函数现在为非阻塞模式
//那么就有两种情况,返回值>0 和 ==0 两种情况
{
if (zpid > 0)
{
printf("parent wait success , zpid = %d\n", zpid);
}
else if (zpid == 0)
{
printf("Parent Running...\n");
sleep(1);
}
}
}
else if (pid == 0)//到了子进程,子进程进入该判断
{
sleep(10);
exit(0);
}
return 0;
}
wait函数和waitpid函数的区别
相比于阻塞回收,非阻塞回收更灵活,可以释放父进程让其执行其他的任务
通过status进行僵尸进程校验
前面只是说了父进程怎么回收僵尸进程,而没有考虑查询子进程退出变成僵尸进程的原因
那么接下来我们说一下通过status进行僵尸进程的校验的过程,先看下下面这张图
校验过程:
1.父进程调用wait/waitpid函数,其中的参数status得到了子进程的退出原因2.进入两个判断,分别是子进程正常退出和子进程异常退出的两个判断
注意,这两个判断不是if--else的关系,而是要用两个if判断
3.如果子进程为正常退出,比如像return 0; exit(0); exit(8)这样退出即为正常退出,那么WIFEXITED(status)的返回值为true,可以通过调用WEXITSTATUS(status)来获取子进程的退出码
4.如果子进程为异常退出,比如像因为硬件访问错误这样退出即为异常退出,那么WIFSIGNALED(status)的返回值为true,可以通过调用WTERMSIG(status)获取杀死子进程的信号编号
以下是通过status进行僵尸进程校验的过程的代码实现
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main(void)
{
pid_t pid;
int i;
for (i = 0; i < 2; i++)//循环创建了两个子进程
{
pid = fork();
if (pid == 0)
{
break;
}
}
if (pid > 0)
{
int status;
printf("Parent pid %d\n Wait..\n", getpid());
pid_t zpid;
while ((zpid = wait(&status)) > 0)//子进程退出校验
{
if (WIFEXITED(status))
{
printf("Parent Wait Success , Zombie pid %d exit error Print ExitCode %d\n", zpid, WEXITSTATUS(status));
}
if (WIFSIGNALED(status))
{
printf("Parent Wait Success , Zombie pid %d exit error Signal No %d\n", zpid, WTERMSIG(status));
}
}
}
else if (pid == 0)
{
if (i == 0)
{
printf("First child pid %d Exit...\n", getpid());
exit(7);
}
else if (i == 1)
{
while (1)
{
printf("Child pid %d while(1) sleep..\n", getpid());
sleep(1);
}
}
}
else
{
perror("fork call failure\n");
exit(0);
}
return 0;
}
今天的学习记录到此结束啦,咱们下篇文章见,ByeBye!