进程的状态详解
- 一、各种状态的概念
- 二、运行状态的详细介绍
- 三、阻塞状态详解
- 四、挂起状态和阻塞状态的关系
- 五、观察各种状态在linux中的表示
- 1.运行态R
- 2.睡眠态S
- 3.暂停态T
- 4.深度睡眠状态D
- 5.僵尸状态Z
- 6.孤儿进程
一、各种状态的概念
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping)(阻塞状态): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠)
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
- 僵尸状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
现在着重讲的三种状态是:运行状态、阻塞状态、就绪状态。
- 运行状态:进程已经占用CPU,在CPU上进行
- 就绪状态:具备运行条件但是由于没有CPU可用,所以暂时不能运行
- 阻塞状态:由于等待某个事件或某个资源而不能运行的状态,如:等待系统调用,IO操作等;
进程三态模型
- 运行态----->就绪态 :时间片使用完或被强行占用
- 运行态----->阻塞态:请求服务器后等待响应,或等待某个信号的到来
- 就绪态----->运行态:进程调度
- 阻塞态----->就绪态:请求服务器完成,或等待的信号已经到来
五大基本状态模型
创建状态:进程刚刚被创建出来的状态
二、运行状态的详细介绍
因为cpu的核心个数是远少于进程个数的,那么进程是如何在CPU里面运行的呢?
-
通过CPU在内核中维护的运行队列来实现的!一般来说一个CPU对应一个运行队列!
让进程入CPU的本质就是将进程入这个运行队列!——但是也不是让进程的代码入队列!而是让进程对应的**PCB(task_struct结构体对象)**入这个运行队列中!!所以让进程去排队本质就是让进程的PCB去排队! -
举个例子 我们去面试我们是将描述我们的信息的简历投到公司的邮箱里面
公司也是通过看一个个的已经排序的简历来挑选的,最后也是通过简历来联系到我们的
CPU的运行逻辑也是一样的!CPU的运行速度是非常快的!所以CPU将运行队列里面的程序全部轮转完一遍也 是很快的 -
因此运行队列上进程就必须随时都准备好让CPU来调度运行它
运行队列里面的一个个进程(PCB)就叫做运行状态
可能这样说很奇怪但是将白了 只要在运行队列里面的进程就是运行状态
要区分一下!不是进程在CPU上跑了才叫运行状态,只要进程在运行队列里面!那么这个进程就叫运行状态,进程状态是为了来区分进程是否在运行队列里面!不是为了区分是否则在被CPU运行!因为CPU运算速度是非常快的所以没有意义!
总结:
一个CPU对应一个运行队列!
让进程进入队列,本质就是将该进程的task_struct结构体(PCB)对象放入运行队列中
进程PCB在运行队列中就是R(linux下的运行状态表示)
那么这个“状态”是保存再哪里的呢?
状态是一种进程的内部属性!那么进程状态也是在**task_struct(PCB)**里面!
一般在内核里面都是用整数来表示一种状态这个整数是几
三、阻塞状态详解
根据冯诺依曼体系知道 CPU虽然十分的快!但是外设的速度相比起来就十分的慢了
进程或多或少的要去访问外设!例如:我们要想写一个程序让显示器显示一句话
但是访问外设的往往都不止有一个进程(如:访问显示器、访问键盘等),所以这时候进程就要进入等待,这样意味着进程不止要占用CPU资源 同时也需要占用外设资源,所以为了应对多个进程的访问!那么每种外设的结构体里面都有和CPU运行队列相似的的等待队列,而此时这个被放在等待队列中的进程的状态我们就称之为阻塞状态
- 这些队列都有各自的硬件维护!
- 当一个CPU正在执行某个进程!而这个进程的某个代码要进行写入操作!那么就要访问磁盘,可是现在访问磁盘的进程已经有很多个了。自然不可能你想访问就访问,必须排队!
- 这时候CPU就不会选择等待这个进程在磁盘中写入完毕,会直接执行下一个进程!(如果等待的话那么就很拖累cpu的速度!降低效率,这是操作系统不想看到的)这样就可以保证CPU永远执行运行的进程!
- 同时原先的这个进程会被从CPU的运行队列中剥离,然后放入等待队列中!
而此时这个被放在等待队列中的进程的状态我们就称之为阻塞状态
注:进程状态的修改实际上就是将进程的PCB放入不同的队列中
四、挂起状态和阻塞状态的关系
从上面我们知道一旦一个进程需要访问外设但是无法立刻访问到的时候,进程PCB就会进入等待队列变为等待状态。
如果有很多个进程同时进入阻塞状态呢?
- 从上面我们知道一旦磁盘空出来进程也不会被立刻调度。而是操作系统先把进程状态修改,然后放入CPU的运行队列里面
- 如果有大量的阻塞状态的进程那么就意味着要等待很长时间(短期内不会被使用),然而这些进程的PCB和程序数据代码都是要占内存的!运行的程序也要占内存
- 当存在大量的阻塞状态的进程时就会导致内存不足。这时想要运行新进程,操作系统就会将这些阻塞的进程的PCB保留在内存但是代码和数据暂时保存在磁盘上!不放在内存里面这样就可以节省一部分的空间!如图:
当某个进程准备完毕以后,会重新加载该进程的代码和数据进入内存,然后将该进程移入运行队列中。
阻塞和挂起状态的区别:
- 挂起是一个行为,而阻塞是进程的一种状态
- 进程存放的位置不同:挂起是将进程移到外存中,而处于阻塞状态的进程还是在内存中
- 原因不同:导致进程被挂起的原因一般是内存不足或者是系统、用户的请求,协调、修改进程,研究进程的状态等,进程阻塞是进程正在等待某一事件发生,可能是等待资源或者响应等(eg.等待I/O完成等)而暂时停止运行
- 挂起对应的行为是激活,将外存中的进程调入内存中。而处于阻塞状态的进程需要其他进程或系统唤醒
- 挂起是被动的行为,进程被迫从内存中移至外存中。而进入阻塞可以看成是一个主动的行为(eg.进程I/O时,进程在等待I/O设备完成时,进程主动进入阻塞状态,I/O完成,进程被激活)
五、观察各种状态在linux中的表示
1.运行态R
运行代码:
#include<stdio.h>
int main()
{
while(1){}
return 0;
}
查看代码:
ps ajx | head -1 && ps ajx | grep a.out | grep -v grep
结果:
可以看到运行态R;
2.睡眠态S
运行代码:
int main()
{
while(1){
printf("hello linux!\n");
}
return 0;
}
结果:
可以看到进程状态S
这里就会有人好奇了,为什么和上面的运行代码对比之下,只是多加了一行打印就是睡眠状态了呢?
原因:CPU的运行速度非常的快,而进程想要打印一行代码就得访问一次外设,访问外设的速度是非常慢的,所以进程每次打印时都要等待IO就绪才能打印,也就是我们说的等待某种资源。所以进程处于睡眠(阻塞)状态;
3.暂停态T
运行代码:
#include<stdio.h>
int main()
{
while(1){}
return 0;
}
结果:
这里运行代码和运行状态的代码一样,这里使用了 kill -19 PID指令,让进程处于暂停状态
关于+号
我们上面也看到了有的进程状态后面有个+号 而有的后面突然就没有了这是怎么回事?
这样的进程我们称为前台进程!也就是说一旦这个程序运行了shell命令行就无法获得命令行解析了!除了使用ctrl+c来停止进程!
而没有+号的进程我们称之为前台进程。
4.深度睡眠状态D
深度睡眠一般是很难看见的!只在高并发高IO的时候常见!
为了方便理解我们先假设有一个进程A 拿着几十万条的用户数据向磁盘写入
磁盘和其他外设不同保存着数据很重要!其他外设可以休眠后随便终止掉!但是磁盘不一样
一般来说当出现大规模的数据IO的时候,一旦出现了内存紧缺,操作系统首先会去挂起进程,但是一旦连挂起都无法解决的时候!那么操作系统就会自主的杀掉进程!
因为内存紧缺导致了进程A被杀掉!但此时进程A正在向磁盘写入数据,那么数据很有可能会发生丢失!
**所以为了防止这种情况!所以有了D状态!**这种状态下操作系统即使是内存紧缺操作系统也不会去杀掉进程!防止数据的丢失!
D状态直白的讲——该状态下的进程,无法被OS杀掉!只能通过断点,或者进程自己醒来来解决!
我们发现linux下其实没有写什么新建,就绪,阻塞因为这些状态都是以操作系统的角度来进行的一个总结!
像是D ,T ,S都可以被归类到阻塞或者挂起!像是运行状态在 linux下就表现为R
5.僵尸状态Z
补充:死亡状态X:当进程死亡以后变成的状态,速度非常的快,我们是捕捉不到的。只要知道有就行了
僵尸状态为什么存在?
首先我们要明白 进程被创建 一定是为了完成某种任务!
当执行任务的时候 我们是不是得知道任务执行的如何呢?当然也有可能不关心
但是不管怎么样,操作系统一定会把任务的结果给保留下来,无论我们是关心还是不关心
- 如果我们想要知道进程完成的怎么样,就代表进程退出的时候,不可以立即释放该进程对应的资源!要保存一段时间让父进程或操作系统进行读取
- 进程从退出到被操作系统/父进程获取读取结果之前保留的资源的这段时间我们称之为Z状态!再被操作系统/父进程读取结果后才会被从Z状态转换成X状态!最后被资源回收!
- 举个例子:当我们在路上遇到了一个因为跑步倒下的人,那个人疲劳猝死了,这时候我们绝大多数人应该会选择报警,等警察来了之后,警察还会让人去做一个医学鉴定,确认这个人已经死亡,一旦确定死亡警察就会让家属去处理后事
而我们把进程比作那个猝死的人 从倒下到完成医学鉴定的这段时间 我们就可以称为僵尸状态,等医学检测出来(操作系统/父进程读取玩结果)就是从Z状态转换成X状态 - 僵尸状态是一个很大的问题!——即进程已经退出但是资源却没有办法被释放这就是一种内存泄漏!!
僵尸进程的危害:
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎 么样了。可父进程如果一直不读取,那子进程就会一直处于Z状态。
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话 说,Z状态一直不退出,PCB就一直都要维护。
- 那一个父进程创建了很多子进程,就是不回收,就会造成内存的浪费,内存泄漏。
- Z状态是不能kill指令杀死的!因为Z状态本身就已经是“死”的没法杀死
解决方法:以进程等待的方式回收处于僵尸状态的进程
6.孤儿进程
孤儿进程:父进程先退出,子进程就称之为“孤儿进程”
- 为什么父进程被杀掉之后不存在僵尸状态呢?——因为父进程也有它的父进程!命令行下面的所有进程都是bash的子进程!父进程被杀掉后直接被bash回收了!我们能看到子进程的僵尸状态是因为它对应的父进程没有进程回收!
- 当一个进程的父进程先退出后,那么这个进程就会被操作系统“领养”,也就意味着此时该进程的PPID变成了1。
- 为什么会被“领养”呢?如果不领养那么子进程对应的僵尸进程,就无人回收资源了!
- 系统领养后,这个子进程的+号会消失,说明这个子进程变成了一个后台进程!——如果前台进程创建的子进程被孤儿了,那么会自动变成一个后台进程!