本文共 2385 字,大约阅读时间需要 7 分钟。
在操作系统内核中,具体的进程状态有:R(运行状态)、S(可中断睡眠状态)、D(磁盘睡眠状态,不可被打断)、T(暂停状态)、t(跟踪状态)、X(死亡状态)、Z(僵尸状态)。那么接下来详细讲解一下僵尸进程(Zombie),它是一个比较特殊的进程状态。
我们先来看一段代码。
#include#include int main(){ printf("---begin---\n"); //进程创建 pid_t ret = fork(); if(ret < 0) { perror("fork error\n"); return 0; } else if(ret == 0) { //子进程 printf("i am child pid=[%d] ppid[%d]\n",getpid(),getppid()); sleep(20); } else { //父进程 while(1) { printf("i am father pid=[%d] ppid[%d]\n",getpid(),getppid()); sleep(1); } } return 0;}
运行: 可以看到父进程的 PID 是 10377,子进程的 PID 是 10378。
分析代码: 通过可以知道,父进程一直在 while 循环,子进程 sleep 个 20 秒会退出。用 ps aux | grep [程序名称]
查看进程信息,可以看到子进程从刚开始的 S(可中断睡眠模式)变成 Z(僵尸状态)。这是为什么呢?
子进程先于父进程退出,父进程来不及回收子进程的的资源,导致子进程变成僵尸进程。而此时子进程的 PCB ( task_struct) 还在操作系统内核中被双向链表管理着,但对于子进程而言,程序已经退出,不能执行任何用户写的代码,子进程的状态会变成 Z 状态,刀枪不入。
僵尸进程对应的进程状态是 Z 状态,并且使用 kill
命令或者 kell -9
都结束不了僵尸进程。
补充: 使用 pstack [PID]
命令可以查看程序的调用堆栈情况
如果产生僵尸进程之后,僵尸进程是不能被 kill -9
强杀杀死的 (因为程序已经退出了)。也就意味着操作系统内核用双向链表还管理着僵尸进程的 PCB (task_struct),操作系统内核维护这样的结构体是需要消耗内存的,程序员还结束不了僵尸进程,操作系统就会内存泄漏。
僵尸进程产生的本质原因是因为子进程先于父进程退出,抓住问题的本质,来看看该如何预防僵尸进程的产生。
1、让子进程不要先于父进程退出。~呃呃呃 ~ 呃~ 这个好像可能性不大,不可取。
2、进程等待。~ 呃 ~ 这个可取,优雅的预防。详细内容参考这篇博文👉
3、将父进程 kill 掉,让僵尸进程成孤儿进程被1号进程所领养,1号进程回收僵尸进程的资源。~ 呃呃 ~ 这个还想不太可取,有点杀敌一千自损八百的感觉。
父进程先于子进程退出,子进程会被1号进程所领养(1号进程又被称之为 init 进程)。1号进程会在子进程退出的时候,回收子进程的退出信息,防止子进程变成僵尸进程。
我们再来看这段代码。
#include#include int main(){ printf("---begin---\n"); //进程创建 pid_t ret = fork(); if(ret < 0) { perror("fork error\n"); return 0; } else if(ret == 0) { //子进程 printf("i amchild pid=[%d] ppid[%d]\n",getpid(),getppid()); sleep(20); } else { //父进程 printf("i am father pid=[%d] ppid[%d]\n",getpid(),getppid()); sleep(5); } return 0;}
运行: 可以看到父进程的 PID 是 19069,子进程的 PID 是 19070。
分析代码: 通过可以知道,父进程 sleep 个5秒会退出,子进程 sleep 个20 秒然后退出,也就是说父进程先于子进程退出。用 ps -ef | grep [程序名称]
查看进程PID,可以看到 5 秒后父进程退出, 随后子进程的 PPID 从刚开始 19069 变为1,也就是说父进程先于子进程退出,子进程被 1 号进程所领养,1 号进程在子进程退出时回收退出信息。
孤儿进程不是一种进程状态!!!而是一种进程种类的名称。
一个进程的进程状态只有 R、S、D、T、t、Z、X。
转载地址:http://mnwxi.baihongyu.com/