进程状态
当我们的进程运行的时候有很多状态,因为我们的CPU资源是有限的,我们的进程必须有选择性的放入CPU上面才可以运行。因此我们的进程就存在了很多种进程状态,比如运行态,阻塞态,挂起态等等。在本次的博客当中我们就来详细介绍一下关于进程介绍以及进程优先级的相关知识。
上述图片就是我们进程当中的许多种状态,由于归类的不同所属状态也不同。我们挑选其中的比较重要的三种状态进行讲解。
运行状态
当我们的代码被运行的时候,进程就会处于运行状态。但是我们会有很多进程需要我们运行,那么我们究竟需要怎样进行选择呢?
在这里我们会将我们的进程的PCB结构体通过链表的形式进行管理。在操作系统当中村咋一个进程的运行队列,其中就包含我们的PCB链表。我们通过一次遍历链表,进行依次让我们的进程运行。
当我们的进程需要的资源都已经准备就绪的时候,我们就将会将该进程的PCB结构体插入到我们的运行队列当中,我们将处于运行队列当中的进程的状态标志位运行状态。
当我们的进程处于运行队列当中的时候,我们可以随时调度该进程。
时间片和进程切换
但是当我们的进程到达CPU运行的时候会一直运行直到程序运行结束吗?并不是,因为这样的话当我们的程序是一个死循环的话。该程序就会独占一个CPU,这样大大浪费了我们系统当中的资源。
所以为了解决这个问题我们采用了时间片的概念。时间片实质上就是一个变量,该变量保存了指定的时间,当我们的程序运行了该时间的代码之后就会强制从CPU上面拿下来,这样就可以避免CPU资源被一直占用的现象发生。
我们将上述进程放入CPU或者从CPU上面拿下的操作叫做进程切换。
阻塞状态
第二个比较重要的状态就是阻塞状态。进程能够进入运行队列当中的条件就是一切资源都已经准备就绪了。而程序进入阻塞状态的条件就在于进程存在部分资源未准备就绪。
举一个简单的例子,当我们在编写一段代码的时候,如果存在scanf函数,我们就需要手动从键盘上输入一段内容,如果我们没有进行此操作。那么我们的进程需要的资源就没有准备就绪,因此此时我们进程就会进入阻塞状态。
当然不仅仅存在键盘资源空缺的问题。因为我们计算机当中的硬件资源都是一定的,但是我们却可以拥有很多进程需要使用该资源。(就比如QQ在视频聊天的时候需要使用摄像头,微信在视频聊天的时候同样需要使用摄像头)
因此我们会创建一个队列,将需要该队列资源的进程的PCB结构体放到该队列的链表当中,之后我们就可以通过遍历该链表,依次分配相应的软硬件资源,是我们对应的程序进入运行状态加以运行。而我们处于等待队列当中的进程的状态就是阻塞状态。
挂起状态
由于我们的计算机资源是有限的,当我们的内存已经快被全部使用的时候,我们的操作系统就会进行将进程的代码放到磁盘当中的操作。因为我们的程序在运行的时候不仅仅会将我们的PCB结构体加载到我们的内存当中还会将我们代码所对应的数据也加载到内存当中。这样就会造成一定程度上的空间的浪费。
因此假如我们的内存如果大部分使用的时候我们的操作系统就会将我们的进程所对应的数据加载到磁盘当中,等到我们想要读取对应的数据的时候再从磁盘当中读出对应的数据。这样就可以环节内存的压力,更好的为我们服务。我们将进程数据被操作系统移到磁盘当中的进程的状态称为挂起状态。
程序运行状态标识符
在前面我们直到了进程在运行的时候会有很多种状态。在Linux当中为了区分这些进程状态,会有一个特定的标识符来确定该程序所处的进程状态。总共一共有六种状态标识符我们依次进行了解。
R(running)运行状态
当我们的程序所需的所有的资源都已经准备充足的时候,我们会将进程的PCB导入运行队列当中,处于运行队列当中的进程所属的状态就是R状态。我们可以通过ps指令进行查看进程的状态。
代码内容如下:
查看得到的结果如下:
前台进程:
我们可以发现进程的状态为R运行状态。但是如果我们会发现状态标识符为R+。并不是我们所说的R。那么加号表示什么意思呢?
加号表示我们当前的进程是前台进程,如果不带加号就代表我们的程序是一个后台进程。
后台进程:
S(sleeping)睡眠状态
该状态可能会是一个新的名词,但是实质上就是我们之前讲到过的阻塞状态。当我们的程序属于阻塞队列,进程正在等待某种资源的时候就会将进程的状态修改为S状态。测试示例如下:
代码如下:
进程的状态为:
我们会发现进程的状态为S状态。我们的进程在等待用户输入一个数据,因此会处于阻塞状态,表现出来的就是进程属于S状态。
但是我们会发现,我们的进程没有scanf之类的输入语句,但是有printf这样的输入语句,我们进程的状态也会是S状态。这是因为输出语句也需要获得输出设备。因为我们资源的有限,请求显示器设备资源的进程有很多所以会将我们编写代码的进程同样放到阻塞队列当中,所以观察出来的是S状态。
当然也可能是R状态,因为我们代码当中并不仅仅存在输出语句,但是我们计算机的计算速度很快,相比于等待资源就绪,代码执行的时间要很小很小,因此我们查找的存在输出语句的进程状态大部分都是S状态,很少存在R运行状态。代码测试如下:
代码内容:
进程执行后的状态:
我们会发现我们进程的状态标识符同样是S状态。
D(disk_sleep)深度睡眠状态
之前我们提到过,我们的进程会向磁盘里面读取或者写入数据。再写入数据完成之后磁盘会向进程返回一个提示,提示进程数据已经书写完毕。在程序书写的时候我们的进程就会处于一种等待休眠状态。
但是如果我们进程的状态标识为S的时候,表示我们的程序处于浅度睡眠。当我们计算机的内存不够的时候,操作系统为了防止我们电脑死机。会主动删除一些进程。如果这个时候操作系统看到进程在休眠,就会下意识的见这个进程kill掉。但是如果我们的进程被杀死之后,磁盘就无法返回相应的数据给我们的进程,就无法得知下一步的操作。因此会自动将数据删除,这样就有可能造成数据的丢失。
因此在Linux操作系统当中,引入了一个D状态,也就是深度睡眠状态,处于深度睡眠状态的进程无法被操作系统删除,这样就避免了我们在数据写入的时候造成的数据的丢失。
由于这个状态要求我们计算机的内存很紧缺,所以我们暂时无法模拟实现该状态,我们只需要有这个概念即可。
t(tracing stop)暂停状态
进程的暂停状态最常使用的方式是在我们的调试的时候。当我们在对进程使用gdb进行调试的时候有时候会需要打断点。在程序运行到断点的时候会自动暂停,这个时候我们的进程就处于t暂停状态。我们可以通过实验进行测试:
Z(zombie)僵尸状态
当我们的程序在运行结束的时候并不会直接将我们的PCB结构体删除,而是经过操作系统检查之后,如果进程是正常结束的再删除PCB结构体。如果不是正常结束就会进行特定的标记。作用类似于我们main函数的返回值。
我们的进程在删除之前需要被父进程进行检查,但是如果当我们的父进程一直被占用,那么就无法将运行结束的进程PCB结构体及时删除,最终造成内存空间上的浪费。我们可以通过一段代码进行测试:
代码内容如下:
程序运行的结果:
X(dead)死亡状态
当一个进程已经运行结束的时候,我们的进程会进入X死亡状态。我们的进程在执行完毕之后会进入僵尸进程,当我们的僵尸进程被父进程释放的之后就会判定我们的进程死亡,进程状态修改为X状态。
孤儿进程
当我们创建一个新的进程之后,如果我们将父进程先退出,但是子进程继续执行。由于子进程的父进程被终止并且被他的父进程(操作系统)所释放,那么我们的子进程再想释放应该怎么办呢?我们的子进程在运行结束的时候会一直处于僵尸状态,造成内存的泄露。
所以我们的操作系统就规定了,当我们的父进程先退出的时候。子进程会自动将父进程更改为1,也就是我们的操作系统。等到子进程释放的时候就会自动被操作系统释放。同样的我们可以通过一段代码进行测试:
代码内容如下:
程序运行效果如下: