本篇总结的是进程的多种状态
对于进程的状态理解,在教材上通常是有下面的思维模式图
那么如何理解上面图片中的内容?
理解状态
如何理解状态?其实理解状态很简单,状态就是PCB
中的一个变量,例如说在PCB
中,可能会有一个变量定义为 int status
,而在代码外面定义
#define NEW 1 #define RUNNING 2 #define BLOCK 3
由此可以可以借助上面的宏定义,将状态进行修改
pcb->status=NEW; ...
因此,所谓状态的变化过程,实际上就是修改整形变量的过程,通过修改整形变量就可以实现修改状态,那这个状态表示有什么用?
if(pcb->status == NEW) { // PCB进入运行队列 ... } else if(pcb->status == BLOCK) { // PCB进入阻塞队列 ... }
进程的状态,其实就是PCB的一个变量,因此状态只和PCB有关系,和代码数据无关
运行状态
运行状态是进程多种状态中的一种,那如何理解运行状态?
只要在运行队列中的进程,状态都是运行状态
上面是对于运行状态的定义,但是又引入了新的概念,什么是运行队列呢?
通常来说,一个CPU
可以负责管理一个运行队列,这里默认设备是一个单核的设备,那么CPU
和内存和操作系统的管理方式如下所示:
每个CPU在系统层面上都会维护一个运行队列
对于上图来说,CPU
就进行维护了一个runqueue
队列,这个队列管理了一串PCB
,而这当中的每一个PCB
又进行管理了它们对应的可执行程序,此时,每一个可执行程序对应的PCB
中的status
数据都是NEW
,代表着这些程序是正处于运行状态,是可以被随时调度的,因此上面这些概念再结合上图其实就是运行状态的概念
阻塞状态
总结了运行状态,就要进行阻塞状态的学习,那什么是阻塞状态?
以下面的例子为例:
#include <stdio.h> int main() { int a; scanf("%d",&a); printf("%d",a); return 0; }
这是一个很简单的程序,输入一个值,再将这个值输出,当开始运行的时候这个程序就进入了内存中,操作系统为它生成了PCB
进行管理它,此时它就变成了一个进程,但是如果一直不输入值,换句话说,键盘上面的数据没有就绪,因此进程要访问的资源没有就绪,此时进程的代码就不能向后执行,这样就被称之为阻塞状态
操作系统作为管理的软件,自然是会在第一时间知道这件事的,它马上知道这个进程不能正常进行了,被阻塞了,因此就把状态修改为BLOCK
,但这并没有结束,操作系统在修改状态的同时就要将这个进程移除运行队列,那移动到哪里?答案是等待队列
那又引入了一个新词,什么是等待队列?这就要回到前面的问题,阻塞状态是如何产生的?
在我们写的代码中,一定会有一些代码是需要访问系统的资源的,比如说最简单的输入输出需要用到键盘和显示器,甚至有些代码还可能用到磁盘网卡等各种硬件的设备,换种角度来讲,从本质来看,是用户想要从这些硬件中获取到用户想要的信息,但是现在用户不输入,键盘的数据没有准备就绪,那么进程需要从scanf中得到的数据就没有就绪,因此就不具备访问的条件,进程的代码不能正常进行,而此时操作系统是知道这件事的,因为在操作系统的底层逻辑中它是可以管理到整个软硬件资源的,因此针对这种硬件设备异常的问题,操作系统也有自己的解决方法
操作系统会将它所管理的硬件设备也进行描述,对硬件设备构建属于它们自己的结构体,因此有了下面的演示:
操作系统会对它所管理的硬件设备也创建它的描述,这里叫为dev
,在这个结构体中也会描述一系列状态,比如说类型,对应到底是什么设备,比如状态,对应现在硬件的情况是什么,比如指针,可以将数据连起来可以进行链式访问,还有下面要提到的wait_queue
前面提到,当一个运行状态的进程被判定为现在是阻塞状态时,它就已经不能再在运行队列中了,那么它应该去哪里呢?这就引出了等待队列的概念,当进程进入阻塞状态的时候,它就会被放到等待队列中进行等待
而当阻塞队列中的进程的状态回到运行状态了,就可以将阻塞队列的进程再拿回到运行队列中即可
因此,进程状态变化的本质?
- 更改
PCB
中status
的变量 - 将
PCB
连入不同的队列中
而由此可以得出一个结论:在操作系统中,会存在非常多的队列,运行队列,等待硬件的设备等待队列等,所有系统内的进程都是用双链表链接起来的
挂起状态
什么是挂起?
前面明确一个概念,如果进程处于阻塞状态,那么就说明,此时此刻它所等待的资源在没有就绪的时候,这个进程是不能被调度的,但这个进程现在依旧处于内存中,如果数量比较少不会造成什么影响,如果数量到达一定程度以至于操作系统内的资源已经严重不足得时候,这时会怎么办?
对于这种情况,操作系统的选择是,将内存数据置换到外设,这个操作是针对所有的阻塞进程的,如下图所示:
交换的位置在哪里?操作系统将信息转移到了swap
分区内,当进程被操作系统调度的时候,被加载到这些分区的代码和数据就会被重新加载进来,也就是说,这是一个换入和换出的过程,而将信息交换到磁盘上后进程的状态,就被称之为挂起状态
弊端在于哪里?
这样的行为一定是有弊端的,由于牵扯到数据的输入和输出的过程,这个过程一定会导致效率降低,但是这是操作系统为了保证整个系统的良好运行而做出的选择,如果不做出这个选择那么整个系统都有挂掉的风险,因此在实际的开发中,swap
分区的大小通常设置和内存的大小一样即可
Linux系统下的进程
本篇在总结操作系统的概念中,大多数情况下是根据的是Linux
下的系统,那么就需要具体的进入操作系统的源代码中去寻找具体的信息:
Linux中进程的状态表示
/* * The task state array is a strange "bitmap" of * reasons to sleep. Thus "running" is zero, and * you can test for combinations of others with * simple bit tests. */ static const char *task_state_array[] = { "R (running)", /* 0 */ "S (sleeping)", /* 1 */ "D (disk sleep)", /* 2 */ "T (stopped)", /* 4 */ "T (tracing stop)", /* 8 */ "Z (zombie)", /* 16 */ "X (dead)" /* 32 */ };
从上面的学习中知道,进程有运行状态,阻塞状态和挂起状态,那么落实到Linux内核源码中,它的实现也是要根据这个原理进行设计
例如在上面的源码中看到,有running
状态,也有sleeping
和disk sleep
状态,也有stopped
和tracing stop
状态,也有zombie
和die
状态,下面就对这些状态进行分析:
状态的解析
R
运行状态:表示的是进程在运行中或者是在运行队列中,也不一定是一定表示为在运行中S
睡眠状态:表示的是进程正在等待一个事件的完成D
磁盘休眠状态:表示的是不可中断睡眠状态,后面进行详解T
停止状态:表示进程被停止了,可以通过信号重新启动等等X
死亡状态:表示进程被杀死了
状态的查看
对于状态的查看可以使用定时脚本来实时监测状态,以及使用ps
指令来进行监视状态
首先写一个C
程序死循环,这样可以实时监测到运行状态
#include <stdio.h> #include <unistd.h> int main() { while(1); return 0; }
# 运行程序 [test@VM-16-11-centos process]$ vim process.c [test@VM-16-11-centos process]$ make gcc -o process process.c [test@VM-16-11-centos process]$ ./process # 使用脚本观察进程状态 PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 365 2397 2397 365 pts/0 2397 R+ 1003 0:05 ./process
此时对于状态一栏显示的是R+
,那么加号代表什么意思?
Linux
中的进程也分为很多种,比如说有前台进程和后台进程,那么带加号的进程就被称为前台进程,前台进程和后台进程最明显的一个区别是,不能输入指令供bash
来解释
在Linux
中,可以使用&
但对应的问题是使用Ctrl+C
不可以终止程序,需要用kill命令来终止进程
休眠状态
在Linux
中还存在休眠状态的进程,其实也就对应着阻塞状态的进程,在这个状态下的进程是不会被运行的,而休眠状态也有两种,浅度睡眠和深度睡眠
浅度睡眠
以前面举的例子为例,当现在运行的程序需要输入一个值的时候,此时如果用户一直不输入值,那么程序就会从运行队列踢出,在等待队列进行等待,此时程序就处于阻塞状态,但如果此时Linux
系统的运行压力过大,它是可以通过杀掉进程来节省资源的
杀掉进程的代价是很大的,如果进程只是一些运行的程序还不算很大损失,但如果是向磁盘写入信息?对于这个进程来说,如果被杀掉了进程,那么对于用户来说是很大的损失
深度睡眠
因此为了避免这样的情况出现,Linux
系统刻意创建了一个disk sleep
状态,它被叫做磁盘睡眠状态,是专门为了磁盘来设计的,也叫做深度睡眠状态,对于进入深度睡眠状态的进程来说,它是不会被随意终止的,操作系统也没有权力对这种状态的进程做操作,这样做的目的就是为了保护和磁盘进行数据交互的进程被操作系统意外终止
终止
在Linux
中存在一个状态是T
和t
状态,现在几乎对这两个系统不做太多区分,这两个状态都是对进程进行暂停,tracing stop
更指向的是对进程进行追踪,例如在debug
模式下追踪断点的这个过程
为什么?
由于在进行软件资源访问的时候,可能会需要暂时对进程停止访问,就将进程设置为STOP
即可
实际运用?
在Linux
的调试工具gdb
中,有一个操作叫做打断点,而打断点的实质就是在debug
程序的时候,可以追踪程序,如果遇到断点,进程就停止了,也就是说调试程序实际上就是进程在运行,而打断点停下来的过程实际上就是进程被终止的过程