目录
一、系统进程状态介绍
1.运行状态
2.阻塞状态
3.挂起状态
二、Linux中的进程状态
1.R (running)
2.S (sleeping)
3.D(disk sleep)
4.T(stopped)
5.t(tracing stop)
6.X(dead)
7.Z(zombie)
三、孤儿进程
一、系统进程状态介绍
上图是对进程状态的笼统的概念,具体在不同的操作系统中的表现是有差异的。
这里先简单解释一下进程状态在操作系统中是如何表示的:进程状态其实就是每个进程所对应的PCB结构体中的一个字段,用整形表示,如下图:
1.运行状态
每个CUP都会有一个对应的运行队列,上面有进程等待被调度执行
只要在运行队列中的进程,都是运行状态。
注意:这里的运行状态包括了就绪,执行,在现在的操作系统中,我们对这几个状态的区分不在那么明确了
2.阻塞状态
在我们过去写过的代码中,都或多或少访问过外设,就拿scanf输入举个例子,当我们的代码执行到该条语句时,会停下来,等待我们从键盘出入数据,那么如果我们一直不输入,它就会一直等待,这种时候,进程所处的状态就是阻塞状态。
所以进程状态变化的本质:1.pcb状态字段的值发生变化 2.pcb从一个队列到另一个队列
那么进程阻塞,我们能"宏观"看到什么?当我们在电脑上打开多个下载任务,同时还在刷视频的时候,会发现很卡,为什么?因为网卡的资源需要被很多进程访问,进程的状态不停的在运行和阻塞之间切换,而这种状态的转变通过卡顿的方式呈现了出来。
3.挂起状态
如果一个进程当前被阻塞了,注定了,这个进程在等待资源的时候,是无法被调度运行的,如果此时,恰好操作系统内的内存资源已经严重不足了,怎么办?
操作系统为了防止自己挂掉,会将该进程的数据和代码先交换到磁盘当中,释放部分内存资源
- 将内存数据进行置换到外设,是针对所有阻塞进程的
- 不同担心和磁盘交互慢的问题,现在的主要矛盾是系统快要挂掉了,需要让系统继续执行下去
- 磁盘中有swap分区---操作系统内的数据会被置换到这里
- 当进程再次被操作系统调度,被置换出去的数据和代码,会被重新加载到内存
这就是阻塞挂起,当然还有其他的挂起(只要当前进程没有被调度,就能被挂起),如就绪挂起等,但是这些挂起被调度的可能性很高,会出现大量的和外设之间的数据交互,降低操作系统的速度
注意:swap分区不能太大,会导致操作系统太过于依赖置换操作,导致操作系统变慢
二、Linux中的进程状态
1.R (running)
当我们在循环打印语句的时候,会发现进程的状态几乎都在S,而不在R,但是我们能看到屏幕在不停的刷Hello world,为什么?其实是因为CPU太快了,进程的状态基本在等待打印,从侧面说明IO(输入输出)相较于CPU是很慢的
而当我们只循环,不打印的时候,进程就一直处于R运行状态,本质是循环判断是CPU执行的,所以进程一直在CPU上运行
这里说明一下+表示该进程是前台进程,特点:能被ctrl+C终止,且bash命令行失效,即不能再执行输入的命令,后台进程的特点与之相反
2.S (sleeping)
休眠状态,浅度睡眠,可以被终止,能对外部信号做出响应。属于阻塞状态的一种。
3.D(disk sleep)
深度睡眠,不能被终止(即操作系统没有能力杀掉这个进程),也属于阻塞状态的一种。
那么为什么要设计这个状态呢?
主要是防止下面这种情况的发生:当进程向磁盘写入数据时,恰好操作系统太忙了,快要挂掉了,这时Linux操作系统会采取杀掉进程的方式来缓解压力,而该进程正好处于阻塞状态被杀掉,但是数据写入失败,这时数据就会被丢弃,造成损失。
所以我们需要设计一个状态不能被杀掉,只能等它完成任务自动醒来,这就是深度睡眠状态
这个状态一般很难观察到,一旦观测到有好几个进程处于D状态,就说明操作系统快挂了
4.T(stopped)
上面的是进程信号,我们可以用18和19两个信号来控制进程的停止和继续(其他的信号我们暂且不管,有兴趣的可以去百度)
一般进程在访问软件资源的时候,可以暂时不让进程进行访问,会将进程设置为T状态
5.t(tracing stop)
当我们在调试程序时,给程序设置断点在运行到断点,程序停止之后的状态就是t状态,需要等待软件资源就绪,也就是说gdb会给程序发送信号,而为什么一个进程能等待另一个进程的资源就绪, 本质是因为进程pcb中也存在进程等待队列
6.X(dead)
死亡状态,也就是终止状态,这是个瞬时状态。
7.Z(zombie)
僵尸状态,就是进程死亡之前的一个状态,用来回收该进程的退出信息,简单来说就是进程运行结束后,我们需要知道该进程的任务完成的怎么样,所以该进程的pcb不会立即释放(该进程的代码和数据可以释放,OS会将它的退出信息写入它的pcb中),需要等待它的父进程回收这些退出信息后,才能释放,这个过程的状态就叫僵尸状态。
我们写main函数都会在最后return 0,就是告诉操作系统,该进程成功结束了,当然也可以返回1,2,3等等,用来表示一些退出信息
下面给大家演示一下僵尸状态
当父进程不去回收子进程的退出信息的时候,子进程会一直处于僵尸状态,造成内存泄漏
三、孤儿进程
上面的僵尸进程,是子进程结束,父进程不回收子进程的退出状态引发的,那如果父进程先退出,子进程又会怎么样呢?
当父进程退出后,bash进程会将父进程的退出信息回收,所以父进程能被正常退出,但是子进程没有被退出,这时它就成了孤儿进程,要被1号进程(操作系统)领养,这里要注意父进程只会对子进程的负责,所以bash作为子进程的爷爷进程,不会回收它,所以子进程依旧不会终止,而是被操作系统领养(这里的父进程和子进程特指代码中父子进程,bash进程和父进程具有父子关系,所以称bash为子进程的爷爷进程)
如果子进程不被领养,那么一旦子进程结束,它的退出信息就无法被回收,该进程就会处于僵尸状态,无法正常退出,造成内存泄漏