Linux------进程状态

前言

在之前,我们学习了Linux为什么要有PCB-----冯诺依曼体系结构与操作系统,先描述,在组织。使用PCB将系统中的资源组织起来,方便操作系统和用户进行管理访问。还学习了Linux中进程的创建和fork操作,现在我们来讲一讲实际的PCB中的重要字段-------进程状态

一、教材中的状态

在很多关于操作系统的教材,我们都可以看到下面这张图,我们能大概知道操作系统的状态有哪一些,做了什么事,但这样也确实会很抽象,如果可以结合具体的操作系统来谈进程状态,就能帮助我们更好理解。

Linux------进程状态_第1张图片

其实这些进程状态,本质上就是PCB中的一个字段,或者说一个变量

#define NEW 1       // 新建
#define RUNNING 2   // 运行
#define BLOCK 3     // 阻塞
struct PCB
{
    //其他变量
    int status;//状态变量
};

操作系统对pcb状态的操作就类似于  pcb->status = NEW 这种,后续就可以通过判断状态的情况,来将进程放入运行队列、阻塞队列或其他队列、链表中里

创建状态和终止状态不必多说,就绪状态和执行状态可以统称为运行状态,阻塞状态分为一般阻塞和挂起阻塞。我们今天主要学习运行,阻塞,挂起这三个状态。

1.运行状态

在我们磁盘中写的程序,执行时会被加载到内存里,操作系统创建PCB将他描述组织起来,当该程序准备资源就绪后,就会被加载到运行队列,CPU会根据运行队列的排队情况,来处理运行中的进程。

Linux------进程状态_第2张图片

我们引出结论:并不仅仅是纸面上的正在运行的进程状态才是运行状态,而是只要在运行队列里的进程,它的状态都是运行状态。 

就是说进程已经准备好了,可以随时被CPU调度运行了。

2.阻塞状态

再学习阻塞状态之前,我们得先了解一个事情,我们的代码中,一定或多或少会访问系统的资源,比如磁盘、键盘、网卡等硬件设备。

我们可能会对文件进行写入,可能需要键盘读取数据,可能需要下载某些任务。

这里最好查看的例子就是通过scanf或者cin进行输入,本质上就是我们需要从键盘中读取数据,当我还没输入的时候,进程需要的资源还没就绪,进程代码也就无法向后运行。

比如下面这个代码

Linux------进程状态_第3张图片

运行之后就会等待键盘输入,此时进程状态就是阻塞状态。

Linux------进程状态_第4张图片

操作系统管理的本质是先描述,再组织,进程有PCB,底层硬件也有相应的设备结构体,存放着设备类型(是什么设备),设备状态(设备是否可以使用)等属性,同时还有一个进程的等待队列,进程排队着等待设备的就绪。此时,该等待队列里的进程状态都是阻塞

Linux------进程状态_第5张图片

 我们知道,操作系统中有非常多的队列,如运行队列,设备等待队列等等。

进程状态变化的本质就是

1.更改pcb中 status整数变量

2.将PCB链入不同的队列中

比如,我需要从键盘中读取数据,目前用户还没输入,资源还没就绪,我将该进程的PCB链入键盘设备的等待队列里,同时将该PCB的status 改成阻塞状态。

等到用户输入结束,键盘资源就绪,再将该进程的PCB链入运行队列,同时将该PCB的status 改成运行状态,让CPU进行调度运行。这就是进程状态的变化。

这些所有的过程,都只和进程的PCB有关,和进程的的代码数据没有关系。

3.挂起状态 

如果一个进程当前被阻塞了,注定这个进程在它所等待的资源没有就绪的时候,是无法调度的,如果此时,恰好操作系统内的内存资源已经严重不足了,该怎么办?

此时可以将进程将在内存中的进程数据置换到外设。直到该进程需要的资源就绪后,再将数据置换回来,这是针对所有阻塞进程

虽然这样会使得操作系统变慢,但是比起操作系统受不了要挂掉,慢一点是可以接受的,更重要的是让操作系统继续执行下去。

在我们装系统的时候,一般都有一个swap分区,操作系统内需要交换的数据会被交换到这里。

这种操作被称之为阻塞挂起,是阻塞的一种特殊情况。

Linux------进程状态_第6张图片

二、linux中的进程状态

在linux 内核2.6版本中,我们看到进程的状态如下。

R(running) 运行
S(sleeping) 睡眠
D(disk sleep) 磁盘睡眠
T(stopped) 暂停
T(tracing stop) 追踪暂停
Z(zombie) 僵尸
X(dead) 死亡

Linux------进程状态_第7张图片

实际上的进程状态,与我们教材上的状态差别还挺大的,也不是教材中说错了,只是实际跟理论的区别。

1.运行状态

我们写一个死循环程序,如下

Linux------进程状态_第8张图片

 还有下面这个在命令行输入的持续监测脚本,目的是在每隔一秒钟查询 mytest 进程的状况。 

while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v "grep"; sleep 1; echo "###################"; done

下面是运行图片,我们发现右侧的监视脚本,状态绝大部分都是S+(“+”号会在后面提到,目前不要管),R+只出现了极少次。这是为什么呢? 

Linux------进程状态_第9张图片

我们知道S是睡眠状态, 等同于前面所讲到的一种阻塞状态。R是运行状态,这样的结果似乎有点出乎意料,这是因为我们不要以人的感知去感知计算机,现在的计算机运行速度非常快,打印的本质是从内存里将数据刷新到显示器上,当需要将数据往显示器上刷新时,显示器不一定是准备好的。

显示器才能刷新多少次,我CPU分分钟上亿次,这完全是不对等的。操作系统一直在将进程从运行队列放到等待队列,又从等待队列放到运行队列,这才构成了我们看见的结果。

如果想看到一直处于运行状态,也很简单,将代码的打印去掉,不要去做  I/O 。

Linux------进程状态_第10张图片

这样就一直都是R(运行状态)。 

Linux------进程状态_第11张图片

前台进程

一般情况下,我们的进程都是前台进程,这样的进程无法在命令行上进行其他指令的输入,但是可以被ctrl+c 终止掉,此时查询状态带了“+”号。

Linux------进程状态_第12张图片

后台进程

后台进程与前台进程相反,输入命令可以显示,ctrl+c终止不了进程,状态后面也不会有“+”号,我们在后台运行进程只需要在  ./可执行程序 后面 + “&”  即可。

Linux------进程状态_第13张图片

2.浅度睡眠状态

S(sleeping):睡眠状态,浅度睡眠,可以被终止,浅度睡眠会对外部的信号做出响应。

在上面我们也有所了解,就不多赘述了。

3.磁盘睡眠状态

 D(disk sleep):也是睡眠状态,深度睡眠,是专门对于磁盘来设计的。

讲个小故事

现在有一个进程,进程中的数据有1个GB,我想将这些数据写入到磁盘当中,磁盘的速度并不快,在开始慢慢的写入,无论成功还是失败,最后都会反馈给操作系统,此时由于进程PCB并不在CPU的运行队列里,而是在磁盘的队列中,因此进程会将自己设置为S(sleeping)状态,如果此时操作系统负荷很大,内存严重不足了(Linux在是在没办法的时候,会通过杀掉进程来节省资源),操作系统发现还有个S状态的进程在这悠哉悠哉的睡眠,他一怒之下就将该进程杀死掉了,于是,程序无法继续执行,后续数据全没了,写入也无法继续下去了,程序就挂掉了。

在这个故事中,操作系统没有问题,他太忙了,内存完全不够用了,就得将某些进程给杀死掉(比如有些时候游戏的闪退)。

进程也没问题,我搁着好好的,都把我杀死了还要我怎样。

磁盘也不粘锅,因为你叫我干活,我干了呀,突然我工具没了(进程终止了,数据没后续了),我也没办法呀。

如果发生这样的问题,资料不重要还好,如果资料特别重要,比如是用户的存款记录,发生了这样的事情,数据全部丢失,直接寄了,这肯定不行。

于是进程状态推出了D(disk sleep),专门针对磁盘设计了深度睡眠,该进程不可被杀掉。操作系统也没资格。 

当然我们一般情况下也是看不见D状态的,因为D状态出现的时候,操作系统已经很忙了,离挂掉不远了,哪里还能让用户看见。

4.停止状态

 我们在命令行输入 kill -l 可以查看关于进程的信号列表,之前我们就是使用的 kill -9 杀掉进程,今天我们还要学习 -18 SIGCOUT 进程继续  和 -19 SIGSTOP 进程暂停这两个信号。

Linux------进程状态_第14张图片

我们可以输入kill -19 + 进程pid 来暂停该进程,同时也看到进程状态变成了T。

Linux------进程状态_第15张图片

此时我们输入 kill -18 + 进程pid 可以将暂停的进程继续执行。 

Linux------进程状态_第16张图片

现在我们知道如何暂停了,那我们为什么要暂停呢?

在进程访问资源的时候,可能暂时不让进程进行访问,但进程有些比较重要的数据,也不能杀掉,因此可以将进程设置为STOP状态

5.追踪停止状态

t(trace stop),追踪停止状态在我们gbd调试的时候,打了断点,然后运行,进程停止在断点处,此时的状态为t。这就相当于我们在调试时,操作系统会一直执行 kill -19 暂停进程,还有kill -18 继续进程这种操作。其实T和t不分家,都是暂停操作,只不过t是调试遇到断点的暂停。

Linux------进程状态_第17张图片

 停止状态和追踪停止状态其实都算是之前我们提到的阻塞状态,他等待的东西跟睡眠状态有点小区别,等待的不是硬件,而是用户的指令。

6.死亡状态

X(dead),死亡状态,这个状态很好理解,就是进程终止了,PCB也没了,我们通过脚本监视基本上也看不到。

7.僵尸状态

当一个进程死亡的时候,他并不会立即释放该进程的数据,而是要等一下再结束。此时的状态就是僵尸状态。

讲个小故事:

一个程序员张三,在敲代码的时候突然一趟,整个人趴在桌子上了,一动不动了,叫他也没有反应,这个咋办,只能打120电话救一救呀,120的人一来,发现人已经不行了,他们叫110来处理后续事宜,警察一来,叫大家都走开,并把现场封锁了,叫上法医来判断张三的死因,采集完周围所有相关信息,最后才会抬走张三等。

在医院判断张三死亡直到警察叫法医来采集信息这段时间,张三已经死亡了,但是人还没被带走,这个状态就被叫做僵尸状态

直到张三被抬走送火葬场,这才叫做死亡状态。

对于进程而言,进程终止了,但是此时PCB信息不能被立刻释放,退出信息会由操作系统写入到当前进程的PCB中,可以允许进程的代码和数据空间被释放,但是不允许进程的PCB被立刻释放,操作系统还得收集这些信息,检查该进程为什么终止,是否完成了他的任务。

收集什么信息我们先不讨论,但我们需要知道,操作系统一般会让退出进程的父进程去搜集信息。这样就通过操作系统或者父进程,读取退出进程PCB中的退出信息,得到子进程退出的原因。比如我们C语言main函数经常会 return 0。这样就知道程序是正常退出的。在进程收集完信息这段时间内,该进程的状态叫做僵尸状态

父进程或者操作系统读取完信息后,PCB状态先被改成X状态,才会被释放

如果一个进程Z状态了,但是父进程就是不回收它,PBC就会一直存在,这样会导致内存泄漏!

下面代码通过fork调用创建进程,子进程运行5秒停止,父进程一直运行,看看这样如何回收?

Linux------进程状态_第18张图片

我们运行看看效果,依然使用如下代码监测

while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v "grep"; sleep 1; echo "###################"; done

通过两个进程的PID和PPID,我们能很轻易的发现谁是父进程谁是子进程,同时当子进程死亡的时候,右边监测窗口看到子进程状态为Z,后面COMMAND那一行多了个defunct(死者),子进程当前状态已经是僵尸状态了,只不过还没有人回收他。

Linux------进程状态_第19张图片

我们修改一下代码,让子进程一直运行,父进程运行5秒退出看看又是什么情况 

Linux------进程状态_第20张图片

我们发现父进程被bash回收了,由于子进程的父进程死亡,他没有了父亲,操作系统来接管了子进程,同时进程从前台进程变成了后台进程。

Linux------进程状态_第21张图片

由此也可以看出,父进程不对孙子进程负责,他只管自己的子进程,因此bash回收父进程就够了,你的儿子我管不了,操作系统去管吧。 

同时像这种父进程死亡,子进程还在的进程,我们称之为孤儿进程。 孤儿进程被操作系统领养了

 

你可能感兴趣的:(linux,运维,服务器)