文章目录
僵尸进程的处理办法
在https://editor.csdn.net/md/?articleId=138925446这篇文章中,我缺失了关于僵尸进程的处理办法的内容,因为当时脑子不好的小菜鸟并未学到这里,现在就让我填上这个坑🕳吧😉
引入
在知道僵尸进程的处理办法之前,请让我们思考以下问题
- 什么是僵尸进程
- 僵尸进程的形成原因是什么
- 僵尸进程的危害是什么
- 怎么处理僵尸进程
问题剖析
什么是僵尸进程
答案:进程状态1为Z的就是僵尸进程(不知道怎么查看进程状态的可以点击“进程状态”右上角的脚注哦)
僵尸进程的形成原因是什么
答案:子进程已经退出,但是父进程退出时并不读取该子进程的状态,使得父进程结束后子进程仍然保持等待父进程来检查其退出状态的状态,但是父进程已经终止了,所以该子进程的Z状态将会一直存在,这个时候谁都拿它没办法,因为子进程已经结束了,就算使用
kill -9 PID
2这种发送终止信号的命令都无效
我将介绍进程相关的常用的三个信号
kill -9 PID:终止进程
kill -19 PID:暂停进程(进程会变为T状态,即暂停或调试状态)
kill -18 PID:继续运行(进程会变为R状态或者R+状态,R和R+的区别是:
R:在后端运行,不会占用命令行,前端照样进行命令行解释,按
ctrl + c
并不会终止程序,要输入kill -9 PID才可终止R+:在前台运行,这种会占用命令行,当进程在前台运行时,你在命令行中输入ls这种命令将不会被响应,因为命令行解释器已经被进程占用了,可用
ctrl + c
终止)
僵尸进程的危害
答案:造成内存泄漏
解释:
在上一个僵尸进程的形成原因
中脑子不好的小菜鸟已经指出:子进程的Z状态将会一直存在,这个时候谁都拿它没办法。
但是进程存在会占用内存资源,但是
- 子进程已经结束----->子进程无法使用该内存
- 该内存被占用-------->其他进程也无法使用该内存
这就导致了这块内存谁也用不了,也就是浪费了,就造成了内存泄漏
僵尸进程的处理方法
答案:使用wait或者waitpid函数
wait和waitpid函数介绍
在Linux中怎么查函数的手册呢?
即答:百度
输入:man [选项] 函数名
wait函数
函数解剖
我们先输入:man 2 wait
【注】:
man wait:这种man直接加要查的内容的方式查找的是shell命令
man 2 wait:这种man加2加要查的内容的方式查找的是手册内容
我们将看见如下部分
NAME
wait, waitpid, waitid - wait for process to change state
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
剖析一下这个部分:
-
NAME wait, waitpid, waitid - wait for process to change state
表示这个函数时用来等待进程的状态改变的
-
#include <sys/types.h> #include <sys/wait.h>
系统库函数
-
pid_t wait(int *status);
- pid_t:返回值
- int* status:一个输出型参数,我将会在后面的waitpid函数部分讲到,在这里我们暂时传参传NULL
当我们在手册页输入:/return val
时3,我们将看见如下部分
wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.
这里表示的是:
- 若wait这个函数成功执行,返回该进程的PID
- 若wait这个函数执行失败,则返回-1
处理僵尸进程的原理
答案:所以当我们用wait这个函数时,当子进程的状态由R或R+状态(运行状态)变为Z状态(僵尸状态)时,wait就会读取到,并发送给父进程------->这就实现了父进程读取了子进程的退出状态------>子进程可以被释放------>Z状态消失,内存归还------>解决了内存泄漏问题
waitpid函数
在这个部分,我会用代码演示父进程怎么获取子进程的退出信息,如下是演示代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();//创建子进程
if (id == 0)//子进程
{
int cnt = 5;
while (cnt)
{
printf("I am child,pid = %d\n", getpid());
cnt--;
}
exit(0);//正常退出
}
else if(id > 0)//父进程
{
sleep(7);
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
printf("等待子进程成功,ret = %d,子进程的退出信号:%d,子进程的退出代码:%d\n", ret, (status & 0x7f), (status >> 8) & 0xff);
}
}
else//创建失败
{
perror("fork");//头文件:<stdio.h>
exit(1);//0:正常结束 非0:异常结束 头文件:<stdlib.h>
}
return 0;
}
函数解剖
同样,我们输入:man 2 waitpid
我们将看见如下部分:
NAME
wait, waitpid, waitid - wait for process to change state
SYNOPSIS
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
-
pid_t waitpid(pid_t pid, int *status, int options);
- pid_t pid:要等待的进程的pid(在这里我们传的是子进程的pid),若该处填入-1,则是等待任何一个子进程
- int* status:输出型参数
- options:默认为0,表示阻塞等待
如果我们用了演示代码,并且同时观察了该进程的状态(我们可以在复制后的窗口输入如下代码来时隔一秒的速度观察进程的状态: while : ; do ps axj | head -1 && ps axj | grep 你的可执行程序名 | grep -v grep; sleep 1;echo "-----------------------------------"; done
),我们将发现如下情况:
当再过两秒后,我们会发现Z状态消失,只留下父进程------->这就实现了对僵尸进程的处理
而我们如何知道子进程的退出码和退出信号呢
我们的演示代码中就有这个片段:
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
printf("等待子进程成功,ret = %d,子进程的退出信号:%d,子进程的退出代码:%d\n", ret, (status & 0x7f), (status >> 8) & 0xf0);
}
聪明的你可能会好奇,为啥有下面这两个东西
- status & 0x7f
- (status >> 8) & 0xff
讲到这里我就要和你讲讲waitpid函数给status赋的值是怎么得到的了
-
当正常退出时:
次低8位表示退出状态,注意这里的次低
-
-
但是我们怎么获取到次低八位呢?😣😣
动动你的小脑瓜🤨
我们是不是可以用位运算呢😲😲😲
那我们是不是可以
- 先将status>>8使得最开始的8~15位位于最低的八位
- 然后再将右移后的数字&0xff呢(因为十六进制的f写为2进制就是
1111
,所以当写为0xff时,就是1111 1111
)
-
-
当被信号所杀(如段错误4、或者被
kill -9 PID
杀死)低7位为终止信号,注意这里的是7位,和上面的不同
-
-
但是我们怎么获取到最低7位呢?
根据上面的做法,聪明的你是不是很快能想到下面的方法呢?
- 直接&0x7f
- 因为十六进制的7的二进制为:
0111
- 因为十六进制的7的二进制为:
- 直接&0x7f
-
这样子我们就能通过status得到子进程的退出状态[5]和终止信号[6]啦
关于退出状态和终止信号:
- 如果得到的值是0:正常退出
- 如果得到的值是非0:非正常退出
- 如果是终止信号非0,则是由于程序崩溃而终止的进程,代码并没有执行完,且我们就可以通过**
kill -l
**来知道该信号是因为什么原因而退出的了- 如果是退出码非0,则是程序运行完了,但是结果不正确而终止的进程**代码执行了,我们就可以通过查看perror**各个返回值来得知是什么原因
结语
- 其实status有32位,但是在僵尸进程这一块我们只需要研究低16位就好了,所以该篇文章只涉及了低16位,对更多的感兴趣的,大家可以自行查阅😶😶
- 我在waitpid的部分中,写了status的构成,示意图中的被信号所杀的情况下第8位是
core dump标志
,这是gdb调试崩溃程序信号,由于脑子不好的小菜鸟还没学到(>人<;),所以暂时没有提到,各位自行查阅