【Linux 学习计划】-- 进程状态 | 进程运行、阻塞和挂起的本质 | 并行、并发与进程切换 | 进程优先级
目录
进程状态
五状态进程模型
运行、就绪状态的本质
阻塞状态的本质
挂起状态
并行与并发
进程切换
进程优先级
结语
进程状态
进程状态的本质是什么?
首先我们知道,在操作系统中,进程是需要被管理起来的,具体则是用一个struct(task_struct)描述进程本身,接着再用内核数据结构将所有的进程管理起来,这也就是所谓的先描述,再组织
而我们的进程状态,本质就是task_struct里面的其中一个属性,仅此而已(比如是一个个的int)
这就是内核中关于进程状态的描述
接下来我们详细讲讲这里面的 7 个进程状态
第一个是R状态
首先需要提前说的是,我们在Linux中是可以用指令查到进程状态的,也就是ps命令
我们来看这样一个代码,我们可以看到这其实就是一个死循
然后当我们将进程跑起来之后,我们再来看一看这个进程的状态:
我们可以看到,这里显示的确实是R(R+中的+代表前台执行,不需要管)
这其实也就代表说,当进程处于R状态的时候,代表进程正在运行或者准备运行
S状态
S状态表示进程正在可中断睡眠状态,简称睡眠状态,s也就是sleep
来看这样一组代码:
我们能看到,这里和死循环的唯一区别就是,他有在打印一行字符串
这时我们将进程跑起来,然后看看进程状态:
我们能看到,仅仅只是将加了一行打印,就变成了一直在睡眠
这其实是因为,我们CPU运行速度过快,而我们的显示器是外设,将数据拷贝到外设的速度比CPU的运算速度慢多了,所以进程大部分的时间并不是在运行,而是在等数据拷贝到显示器上,然后再运行
所以从理论上来说,如果我们运气好的话,是能看到一两个R状态的,只不过主包今天运气不是很好,就不做演示了
T状态
可以看到,我们现在用kill命令将进程停止了之后,进程的状态自动就变成了T,这也是停止状态
t 状态(tracing stop)
这个状态其实也是停止,和上面的 T 状态其实差不多
但是这个和 T 不一样的就是,他是调试专属的状态
试想一下,我们的调试本身就是进程已经在跑了,然后我们给他打断点,让他停在某一个位置,所以这其实也是一种进程的暂停,如下图:
左边是在用gdb调试代码,右边显示状态的时候就是 t 状态(前提是gdb是用 r 跑起来的并且打了断点)
z 僵尸进程
什么是僵尸进程呢,先来举一个例子:
如果一个人si了,那么警cha到了之后第一反应应该是封锁现场,等法医过来收集完信息之后,才会清理现场,办理后续的各种事情
而我们的进程也是一样的,如果一个进程没了,那么也需要有进程回收他,而这个进程一般情况下都是父进程(孤儿进程或者waitpid都是os直接回收)
那是否有这么一种情况,父进程还在,但是子进程已经挂掉了,这个时候父进程还没有干完自己的事情,所以是没有办法回收子进程的,只有等父进程也退出之后,才会回收子进程
那如果父进程一直不回收子进程,那么子进程这个时候将会一直是僵尸进程
如下:
我们写一个简单的fork,然后我们再在外面使用kill -9直接将子进程杀掉,这时候再去查看子进程的状态,我们就能看到 Z 状态了
可以看到,当我们将子进程给 kill 掉之后但是父进程一直不回收,这时候子进程将会一直是 Z 状态
而且很关键的一点是,如果我们在未来写了一些项目几万行代码,你甚至都不知道有僵尸进程的问题,那么父进程一直不回收,子进程将会一直 Z,Z 的时候,子进程的代码和数据都没释放掉了,之后对应的task_struct还在,因为要等父进程拿完对应的子进程的信息之后才会被释放
那么,子进程将会一直占用内存资源,这也就造成了一个大问题——内存泄漏
而在未来,我们将会学到使用waitpid等待,最后子进程完成任务退出之后将会被等待,最后直接被os拿信息接着释放,这样就不会内存泄漏了,但这不是今天你的重点,这里就不做讲解了
X状态(死亡状态)
这是一个瞬时状态,只会出现在进程死亡的一瞬间
D状态(不可中断睡眠状态)
这个状态和前面的 S 状态其实多多少少有点联系,这里就讲一个小故事:
当我们有重要的业务的时候,我们的进程就是D状态
比如在银行,如果今天这个进程要将一万个用户的转账数据放到内存中,但是如果放到一般,因为如果没有D的话,那么进程就将是S状态(睡眠),所以如果OS比较紧张的话,那么就有可能将这个进程直接kill掉或者直接打断然后派发其他任务,那么这时候数据就流失了
所以这种时候就是D状态,意味着不可被打断,你就是再怎么紧张,你也不能动我,这就是D
孤儿进程
这是进程的一种,有这么一种情况,也就是,如果父进程没了,但是子进程还在呢?那么子进程这时候如果也没了,那么数据应该被谁回收
首先父进程如果是第一个在程序里被开辟的,那么父进程的父进程就是bash,他会被bash回收,那么这时候子进程就变成了孤儿进程
直接说结论,这时候子进程会被 1 号进程(其实就是bash)领养,最后由bash进行善后处理
五状态进程模型
我们会看到,这个五状态进程模型,和我们上面讲的各种状态一个都对不上啊
其实不是对不上,而是因为本身的性质就不一样
我们的五状态进程模型是理论,而上面说的S、D、t、T等等,这些是Linux的具体实现
比如运行状态和就绪状态,这不就是R吗,阻塞,就是S等等
接下来我们直接来讲一讲,运行状态与就绪状态的本质,阻塞状态的本质,挂起状态的本质
运行、就绪状态的本质
我们来看这样一张图片,首先我们在内存中是有一个runqueue的,也就是运行队列
我们将进程管理起来之后,还需要将等待运行的队列放进runqueue这个内核数据结构中管理起来
这也就代表,只要你是被链进runqueue里面的进程,那么就是就绪和运行状态
当然你也可以这么理解,因为后续进程还需要被放进cpu中执行相关任务,你可以理解成:放在runqueue中但是还不在CPU的就是就绪状态,也就是已经准备好了,等待运行,而被CPU调度的进程则是运行状态
而在Linux中,无论是就绪状态还是运行状态,都是用 R 来表示的
阻塞状态的本质
在我们之前写代码的时候,一定会用过scanf函数
当我们将程序跑起来的时候,如果我们不在键盘上输入数据,那么进程将会一直停在那个地方
这其实就是所谓阻塞状态了,而这个状态下,进程自然就不会停留在runqueue中
那么进程会在哪里呢?
首先我们的外设都是硬件,而操作系统如果要管理外设的话,就需要驱动层的帮助,而驱动层是经过struct描述且用特定数据结构管理起来的软件
而我们外设的驱动中会有这么一个字段,叫做wait_queue(等待队列)
当我们的进程需要用外设参与操作的时候,这时候在我们看来,其实就是阻塞了
而阻塞的进程就不应该再浪费CPU资源了,所以就不能放进runqueue里面,而是会被放进对应驱动软件中的等待队列字段里面
如此一来,当我们的外设完成了对应操作之后,进程重新加载到runqueue中,这就是阻塞状态的本质了
挂起状态
当我们的内存空间过于紧张的时候,就会有挂起状态的出现
我们在磁盘当中有一个区域叫做——swap分区
当我们的磁盘空间严重不足的时候,就会出现这么一个情况:一个进程可能暂时在执行别的不需要他管的任务,或者是一些其他的进程,os 会将他们全部移动到磁盘(外设)的swap分区上
这样省出来的空间,就可以供操作系统做别的事情
等晚些这个进程要被用到的时候,再从swap分区中移动回来
但是这也会导致一个问题,也就是效率
在日常使用的感受就是——卡
因为OS中最消耗时间的就是拷贝数据问题了,现在还要将数据从内存拷贝到外设,要用的时候再从外设拷贝回来,这显然占用了很大一部分的效率
但是注意,这里的前提条件是,内存很紧张,所以这也是一种时间换空间的做法
并行与并发
我们刚刚说到了runqueue,那么进程在操作系统中到底是怎么运行的呢?
我们看到这张图,我们的进程会一个一个地被加载到CPU里面接着进行调度
但是又有不一样的地方,即他是用时间片轮转的方式进行进程调度的
一般来说,如果我们将一个进程调度完之后,才跑下一个进程,那其实不是很合理且不现实
比如我们今天打开一个网易云音乐,这个软件现在正在跑,我们就打开不了其他软件了吗?当然不
所以在OS中,他会给每一个进程一个特定的时间段,过了这个时间段就直接中止这个进程转而执行下一个进程
一直反复这个过程,只要转换的速度够快,我们人其实是察觉不出来的,就达到了每一个软件,每一个进程都在同时跑的效果
这就叫做并发
那么什么是并行呢?
我们上面的并发是只有一个CPU的情况下,很多进程在一个CPU下进行轮转调度
而并发就是现在不止一个CPU,由多个CPU同时在进行进程轮转调度的工作,这就是所谓并行了
进程切换
这个其实是接着并行与并发这两个知识点的,我们要讨论的是,进程的并发是如何进行的
我们来看这张图,首先我们进程要想加载到CPU中的话,数据是必须要拷贝到CPU中的
而CPU中的寄存器就可以暂时保存这些数据
接着,当我们这个进程运行到一半的时候,突然时间到了,他需要被切换了,那么他就需要将这些数据全部保存下来,不然的话,下次再轮到他的时候,不就相当于没跑过吗
为了保存这些数据,所以我们的PCB也就是task_struct中有专门的结构体类型,就是用来保存上下文数据的
当我们要切换进程的时候,CPU里面的寄存器的数据就会被拷贝到task_struct中的特定字段,然后再运行下一个进程
等到下一次再回到这个进程的时候,就可以将上一次保存下来的上下文数据全部放进去继续跑了
这就是所谓进程切换了
将个故事:你是一名大学生,今天你想去当兵,那么你就需要先和辅导员打个招呼,这就相当于是将CPU中寄存器的数据加载进进程的特定字段中,然后你就去入伍了。两年回来以后,你会学校还是要和辅导员打个招呼,接着就是恢复学籍数据等等,这一步其实就是将进程中的数据重新加载回CPU进行调度了
进程优先级
顾名思义,就是哪个进程最先调度
更重要的进程就需要更高的优先级
那么和上面的进程状态一样,进程的优先级其实也只是进程PCB里面的一个字段而已,这个我们可以在命令行中直接查看到:
(使用的是 ps -al 命令)
但是这里面有两个值得注意的字段:一个是最新的优先级的值,还有一个就是nice值:
这也就意味着,我们的进程优先级是可以更改的,只不过更改的方式只是更改nice值
这里更改nice值有很多方式,比如renice,但是主包这里直接用 top -> r -> 进程pid -> 你要的nice值
但是我们可以看到,这里面的优先级最多就是到了99,而nice值则是19
这是因为我们的nice值是有范围的,也就是 -20~19
再加上默认的进程优先级就是80,所以进程优先级的范围就是 60~99
结语
这篇文章到这里就结束啦!!~( ̄▽ ̄)~*
如果觉得对你有帮助的,可以多多关注一下喔