【进程管理】进程状态

一.什么是进程状态

进程状态是PCB中定义的一个字段,具体到LInux操作系统,就是task_struct结构体中的一个变量,所谓的状态变化,本质就是修改整型变量。例如:

#define NEW 1

#define RUNNING 2

#define BLOCK 3

……

int status = 2;//运行状态

二.进程的三种状态

 

以上是在网上找的图,这些图基本都是教材上的,然而你会发现,不同教材进程的划分方式不一样,到底该听谁的呢?

其实不同的操作系统的状态分类是不一样的,但是原理都是大差不差,所以我们要做的就是学号原理。接下来我说的三种状态就是操作系统的原理层面,讲解完后我会再结合具体的LInux操作系统来分析,最终你会发现L虽然Inux操作系统的状态有那么多种,但都是从这三种上面衍生过来的。

1.运行状态

在操作系统内部,每个CPU都对应着一个运行队列,CPU和这个运行队列相关联,CPU要调度进程时直接到运行队列中获取

只要在运行队列中的进程 ,状态都是运行状态。也就是说运行状态不一定是真正意义上的运行,而是说这个进程已经准备好了,可以随时被调度。

2.阻塞状态

我们写的代码中,一定会或多或少地访问系统中的某些资源,比如磁盘,键盘,显示器等硬件设备。

加入代码中有一段使用scanf从键盘读取数据,代码跑到这就停了下来,等待用户输入数据。但我就是不输入,键盘上的数据没有就绪,不具备访问条件,进程的代码就无法向后执行。

进程要访问的资源没有就绪,操作系统要不要知道呢?当然要知道,不只要知道,所谓的不具备访问条件,就是操作系统告诉你的。操作系统不仅仅要进行进程管理,还有驱动管理来管理硬件。

和进程管理类似,要想对硬件进行管理,就得先描述再组织,所以每一个硬件设备也有对应的结构体来描述它的属性。所以硬件的资源有没有准备好,操作系统是一清二楚的。

操作系统是这样做的:当硬件资源没有就绪时,将进程的PCB从运行队列中移除,放到硬件设备的等待队列中,并将进程状态设为阻塞状态。当硬件资源就绪时,操作系统也是最先知道的,这时再将进程的PCB从等待队列移到运行队列,进程状态更改为运行状态,随时准备被调度。

没错,硬件也有自己的队列,事实上,操作系统中存在非常多的队列:CPU的运行队列,等待硬件设备的等待队列

这里我们得出一个重要结论:进程状态变化的本质就是:

  1. 更改PCB中的status整型变量
  2. 将PCB链入不同队列中

当一个进程阻塞了,我们看到了什么现象?为什么?

我们会看到进程卡住了,因为PCB不在运行队列中,进程不是运行状态,不会被CPU调度。

生活中,当我们执行多个下载任务时就会很卡,这是因为很多个进程都要向网卡获取数据,网卡忙到停不下来,很多进程大多数时候都处于阻塞状态。

补充说明:一个进程的PCB可同时在多个链表中

我们写的链表,next结点只有一个,因为结构体里的next指针只有一个,但是PCB在实现时,将prev,next这些链接字段又用了一层结构体封装了,所以一个PCB里可以有多个prev,next指针。

3.挂起状态

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

当操作系统内存资源严重不足时,操作系统会将某些处于阻塞状态的进程的代码和数据置换到磁盘中,以腾出更多空间来使用,这时进程所处的状态就叫做阻塞挂起。当进程被调度,曾经被置换出去的进程代码和数据,又要被重新加载进来

问题一:这样不会变慢吗?

肯定是会变慢的,但这是操作系统生死存亡的时候啊,慢些又算得了什么呢?

问题二:数据换到哪里了?

换到了磁盘上的swap分区。建议不要把swap分区设置得太大,因为空间太大了,操作系统会更加依赖这种挂起进程获得空间的方式,频繁地和磁盘交换数据会降低效率。swap分区设置成和内存大小一样即可。

三.Linux操作系统的进程状态

以下内容来自Linux某个版本的源代码:

static const char * const task_state_array[] = 
{
    "R (running)",
    "S (sleeping)", 
    "D (disk sleep)", 
    "T (stopped)",
    "t (tracing stop)",
    "X (dead)", 
    "Z (zombie)", 
};

1.R运行状态

写一段平平无奇的helloworld,运行后打开另一个窗口,用ps命令查看一下

  1 #include <stdio.h>
  2 
  3 int main()
  4 {
  5     while (1)
  6     {
  7         printf("hello world\n");                    
  8     }
  9     return 0;
 10 }

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

用这样一段Shell脚本来监控进程,也就是一秒钟打印一下信息。

然后发生了戏剧性的一幕,左边code进程在疯狂刷屏,右边进程的stat怎么不是R呢?这个S+是个什么鬼?

S是休眠状态,就是一种特殊的阻塞状态。因为printf操作是在访问显示器外设,在执行IO操作,IO操作和CPU处理处理速度相比是很慢的,所以这个进程大多数时间是在阻塞状态等待显示器就绪的。而监控脚本一秒钟打印一次,所以检测到运行状态的概率很小。

  1 #include <stdio.h>
  2 
  3 int main()
  4 {
  5     while (1)
  6     {
  7     }
  8     return 0;
  9 }

当把printf去掉,再次运行代码,监控进程,得到的结果就是这样的了:

为什么是S理解了,但后面加号是什么呢?+表示这个进程是前台进程。这种进程一旦启动就会导致命令行解释器无法接收命令,并且可以Ctrl + C终止的,就叫做前台进程。要想一个进程在后台运行,只需启动时加上&。后台进程运行时,我们仍然可以在命令行中输入命令,并且这种进程不能用Ctrl + C终止,但是可以用kill命令杀死

./code &

2.S睡眠状态

前面讲的阻塞状态是一种统称,具体到Linux系统,S睡眠状态就是所说的阻塞状态。S状态是一种浅度睡眠,会对外部信号做出反应,可以被kill掉。与浅度睡眠相对的还有深度睡眠

3.D磁盘休眠状态

D休眠状态也是阻塞状态,它是一种深度睡眠,是专门针对磁盘来设计的。D状态是为了防止进程向磁盘写入关键数据时,该进程被操作系统杀掉而导致数据丢失。深度睡眠的进程不可被杀掉,即使是操作系统也不行。只有这个进程完成了写入工作自动醒来,除此之外没有任何办法,关机都关不了。

一般而言,D状态我们是查不到的。如果你查到了D状态,说明你的磁盘已经非常忙了,计算机快要挂掉了。

4.T停止状态

kill不仅可以杀死一个进程,也可以把一个进程暂停,使其进入T停止状态。

kill -l

如图可以使用kill指令给进程发送各种信号,前面是编号,后面是名称。这些信号其实是操作系统中的宏定义。其中19号信号就能使进程stop。

kill -SIGSTOP [pid]

kill -19 [pid]

以上两个指令都能暂停一个进程,但这个进程并没有被杀死,如果它是前台进程,就会将它移到后台。

kill -SIGCON [pid]

这个指令可以解除进程的暂停状态,使其继续运行。

S状态存在的意义是什么呢?

在进程访问软件资源的时候,操作系统可能暂时不让进程访问,就将进程设为T停止状态

T状态和D状态一样,用户基本是见不到的。

5.t追踪停止状态 

T和t可以基本看成是同一个状态,T不常见,当t比较常见。

例如用gdb来debug程序时,追踪程序,遇到断点,进程就暂停了,此时就处于t状态。

其实T/t状态也是一种阻塞状态,进程debug时停下来,其实是在等待gdb给它下一步的指令,是在等待gdb这个软件(进程)的资源。当我们输入n给gdb,gdb就会给进程发送kill SIGCON指令,软件资源就绪,进程又会继续运行。

所以说等待队列不只是CPU和各种硬件设备拥有,进程PCB也是可以有自己的等待队列的!进程也可以等待另一个进程

6.X死亡状态

当一个进程结束时就处于X状态,这是一个瞬时状态,用户很难查到

7.Z僵尸状态

我们创建一个进程的目的是什么?是为了完成某项任务。那么一个进程在退出时,是不是理应告诉我任务完成的情况如何呢?是这样的。

当一个进程退出后,它的退出信息会被操作系统写入到进程PCB中,进程的代码和数据会被立即释放,但是PCB仍然被操作系统维护。只有父进程读取了退出信息,得知进程的退出原因,PCB才会被释放。像这种进程已经退出,但是退出信息未被父进程读取,PCB一直存在的进程就叫做僵尸进程,所处的状态就做僵尸状态。当退出信息被读取之后,操作系统会先将PCB设为X状态,然后释放PCB。

僵尸进程的危害是什么? 

一个进程已经退出,但是它的父进程就是不读取退出信息,PCB就会一直存在,占用内存空间,造成内存泄漏。

四.孤儿进程

当一个进程的父进程退出时,他就变成了孤儿进程。孤儿进程必须要被领养,否则他的退出信息无法被读取,会一直处于僵尸状态,造成内存泄漏。孤儿继承统一被一号进程systemd领养,1号进程就是操作系统

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值