摘要:本章我们将认识几种进程状态——运行状态、休眠状态、暂停状态、退出状态等。还要介绍两种具有惨烈身世的僵尸进程与孤儿进程~
本文分享自华为云社区《僵尸进程?孤儿进程?为什么他有如此惨烈的身世...》,作者: 花想云 。
Linux中进程状态一般有:
下面为这些状态在kernel源代码中的定义:
static const char * const task_state_array[] = { “R (running)”, // 0 “S (sleeping)”, // 1 “D (disk sleep)”, // 2 “T (stopped)”, // 4 “t (tracing stop)”, // 8 “X (dead)”, // 16 “Z(zombie)”, // 32 };
ps axj | head -n1 && ps axj | grep myprocess | grep -v grep
接下来我们就依次来看各种状态是什么模样吧~
当你在电脑上同时运行很多程序,例如你敲代码的时候,还听着某个软件播放的歌曲,或者在浏览器之间来回切换。请问此时这些所有的应用都在CPU运行吗?
答案是,并不是这样的。
在CPU进行工作的时候,会存在一个进程运行的队列。队列维护的内容是一个个task_struct结构体的指针(上一章中讲到了task_struct为进程描述符)。在该队列中维护的进程都处于R状态,且等着被CPU所调度。
写下一段简单的代码:
#include<stdio.h> #include<unistd.h> int main() { while(1) { printf("hello myprocess\n"); } return 0; }
在运行该程序之后,查看该进程的状态如图所示:
那么有什么办法等看到R状态呢?我们将上面的代码略作修改:
#include<stdio.h> #include<unistd.h> int main() { while(1) { //printf("hello myprocess\n"); } return 0; }
如上图所示,当我们不再访问外设,而是只不停地做重复的运算,此时CPU会一直被调度,就能看到R状态了。
S状态称为休眠状态。一个进程好端端地为什么要休眠呢?难道是因为运行太久累到了吗?当然不是这样。休眠状态本质是一种阻塞。
例如,当一个进程运行到一半,需要从磁盘上获取很大的一块数据,那么就要花费较久的时间。此时OS的处理方式是,让该进程继续等待它要的数据,但是要求你不能在等待资源的时候还占用着CPU,于是该进程就被OS安排到某个地方进行等待,这时该进程就处于S状态。
#include<stdio.h> #include<unistd.h> int main() { while(1) { int n = 0; scanf("%d",&n); printf("%d\n",n); } return 0; }
如上图所示,当进程等待用户从键盘上输入的数据时,它就处于睡眠状态。
D状态也是一种休眠状态,但是它又有个名字叫做磁盘休眠状态或者不可中断休眠。那么如何看待S状态与D状态的区别呢?
首先我们得清楚一般什么情况下进程会发生中断。当一个进程偷偷地地干一些坏事,此时用户想停止该进程,那就要向该进程发送一个中断信号,该进程就被“杀”掉了。
在一些情况下,不需要用户自己动手,OS自己就能“杀”掉某些进程。例如,当内存资源非常紧张甚至危险到了整个系统的安全时,OS就会“杀”掉一些不太重要的进程。
就比如某个进程因为在等待数据而进入休眠状态,此时被OS发现了,内存这么紧张你还在这睡懒觉?叉出去!好嘛,进程被叉出去了。此时数据被读到一半,结果当事人没了。这些数据只能被舍弃,不然谁找到刚刚那个进程投胎之后还能不能找到“我“。
这些被舍弃的数据若是一些无关紧要的数据也就罢了,丢就丢了。但若是什么机密文件那岂不是坏了大事了?所以,为了避免将某些不能中断的进程被OS误杀掉了,可让该进程处于不可被中断休眠状态即D状态。此时该进程休眠时终于不怕被打扰了,但是,各退一步,我换个地方睡,不然我怕你急眼。于是该进程休眠时,就在相对宽阔的磁盘当中去休眠了。
T状态称为停止状态,非常好理解,就是让某个进程暂停一下。例如在调试时,我们设置了几个断点。当进程在该断点处停下来时,该进程就处于暂停状态。
方法一
#include<stdio.h> #include<unistd.h> int main() { while(1) { //printf("hello myprocess\n"); int n = 0; scanf("%d",&n); printf("%d\n",n); } return 0; }
当我们在第9行打上断点并运行后,程序停到了断点的位置。此时查看进程状态如下图所示:
注意:t也是一种暂停状态。有时候也被叫做追踪状态。
方法二
我们可以通过给进程发送暂停的信号使进程进入暂停状态。编辑如下代码:
#include<stdio.h> #include<unistd.h> int main() { while(1) { printf("hello myprocess\n"); } return 0; }
当程序开始运行后,此时向进程发送暂停的信号:
$ kill -19 (进程PID)
此外,我们还可以发送继续的信号让该进程继续执行:
$ kill -18 (进程PID)
注意
进程继续在运行了。但是我们发现有一个地方好像和之前不一样了,S后面是不是一直有一个+号来着?我们也不知道+是干嘛的,只知道他现在好像消失了。
之前我们在终止一个程序时,习惯使用Ctrl + c ,但是现在好像对于后台在运行的进程失效了,此时我们需要掌握一条新的指令来”杀掉“进程:
$ kill -9 (进程PID)
或者,
$ kill -9 (进程PID)
概念有点多,先来理一理。首先什么是退出状态码?在一段C语言程序中,我们经常要在main函数结束时写一句代码——return 0; 。这个0就是退出状态码,但并不仅仅是0,还可以是1,2,3…
接下来我们就写一段代码看看僵尸进程:
#include<stdio.h> #include<unistd.h> int main() { pid_t id = fork(); if(id == 0) { while(1) { printf("我是子进程,我在运行,pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } } else if(id > 0) { while(1) { printf("我是父进程,我在运行,pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } } return 0; }
当我们运行程序后,能看到程序正常的在运行;
此时当我们执行指令将子进程”杀“掉,子进程就会变成僵尸进程;
$ kill -9 (子进程PID)
其中我们能看到一个英文单词——defunct就是僵尸的意思。
僵尸进程是有危害的,当然我们也可以避免它,这就需要在下一章节中提到了。
当父进程活着,子进程提前挂掉,容易造成僵尸进程。那如果父进程提前挂掉,子进程又该何去何存呢?这就是我们接下来要讲的孤儿进程了。
编辑如下代码:
#include<stdio.h> #include<unistd.h> int main() { pid_t id = fork(); if(id == 0) { while(1) { printf("我是子进程,我在运行,pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } } else if(id > 0) { while(1) { printf("我是父进程,我在运行,pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } } return 0; }
运行该程序,我们使用kill命令”杀“掉父进程,此时再来查看进程信息:
如上图所示,子进程发生了两个变化。一是子进程的PPID,二是子进程变为在后台运行了。
如何理解
当子进程的父进程挂掉之后,子进程会被1号进程领养。该进程也被称为孤儿进程。
答案是,找一个人为自己收尸。不然当哪一天自己突然挂掉,没人为自己收尸那么就会变成为祸人间的僵尸进程了。