linux入门---进程状态

文章详细阐述了进程的多种状态,包括运行、阻塞、挂起、浅度睡眠、深度睡眠、暂停、追踪停止、僵尸和死亡状态。讨论了状态之间的转换,特别是在资源管理和CPU调度中的角色,以及Linux系统中对这些状态的特定表示。
摘要由CSDN通过智能技术生成

什么是状态

我们的生活中的很多事情都会存在一些与之对应的状态,比如说商店里面的物品就会存在着待售卖的状态,已卖出的状态,卖出已退回的状态等等,学校里面的学生就会有学习状态,放松娱乐状态,期末复习状态,摆烂不想学状态等等,我们发现不同的事物在不同的场景下会有着不同的状态,而不同的状态就会使得这个事物要做或者被做一些事情,比如说一台电脑要是处于待售卖状态的话,那么他要干的事情就是呆在仓库的货物架上,如果电脑处于已被售卖的状态的话,那么他要干的事情就是呆在用户的身边执行者用户给他的指令,如果一台电脑处于卖出但退回的状态时,那么电脑要干的事情就是在厂家的质检处等待着工程师的全面体检,所以一个东西要是处于不同的状态下,这个东西一定会在不同的场景下做或者被做一些事情,那么这就是状态,我们上面举的是电脑和学生的例子那么电脑中的进程也是同一个道理,当一个进程在不同的场景下干不同的事情我们也可以将其称为状态,那么接下来我就要带着大家学习进程有哪些状态,并且进程在不同的状态下要干或者被干哪些事情。

进程有哪些状态

大家在不同的书上或者不同的版本的操作系统中会看到进程中有很多不同的状态,比如说:运行状态,新建状态,就绪状态,挂起状态,阻塞状态,等待状态,停止状态,挂机状态,死亡状态等等大家可以看到这里的状态十分的多,而且有些状态好像是一样的,比如说挂机状态和停止状态理论上有区别吗?新建状态和就绪状态有区别吗?对吧看上去好像没什么区别对吧,所以我们上面所说的一些状态其实是操作系统的一些说法并不是指的操作系统内核中的实际状态,就好比中文中的你好可能在中国的不同地方有着不同的说法,但是不管怎么说这些话都是一个意思:你好,所以接下来的学习中我会带着大家从普遍的操作系统层面上理解上面的不同状态。

准备工作

首先我们知道计算机的底层是一堆硬件组成,这些硬件各有各的功能,但是他们有个共同的特点就是他们的管理者都是操作系统,但是管理者是不会与被管理者直接接触的,所以在操作系统和硬件之间还有个东西叫做驱动,驱动的作用就是执行操作系统下达的指令,以及将收集的底层数据上传给操作系统以方便更好的下定决策来管理底层,而管理的本质就是对数据进行管理,管理的方法就是先描述再组织,底层硬件产生的数据都有着相同的属性和特点,所以操作系统为了更好的管理数据就会创建一个结构体来描述硬件产生的数据,驱动要干的事情就是将这里产生的各种结构体对象上传给操作系统,这样操作系统对底层硬件的管理就变成了对底层硬件产生的数据进行管理,对底层硬件的管理就变成了对结构体对象进行管理,管理的方法就是对结构体对象所组成的链表进行增删查改,我们可以通过下面的图片来理解理解:
在这里插入图片描述
把磁盘中的一个程序加载进内存时操作系统会创建一个内核结构体对象来描述加载进内存的进程,我们把这个结构体对象称之为PCB
在这里插入图片描述
以上就是我们之前讲的全部内容想必大家应该能够理解,那么接下来就要在这个基础上来理解不同的进程状态。

运行状态

当cpu要执行一个进程时,他会通过进程的PCB来找到相应的程序在内存上的位置,然后再将程序的内容加载进内存,但是这里就会存在一个问题:进程都在内存里面的但是cpu的数量是远远小于进程的,所以这里就会存在一个问题:cpu如何管理好这么多进程?有了之前的基础想必这个问题并不难,因为PCB本身就是一个内核结构体,并且结构体的内部还含有指向同类型的结构体指针,这样不同进程的PCB就可以通过结构体内部的指针连接起来形成一个队列,而cpu要干的事情就是创建一个可以维护进程队列的结构体对象,通过这个对象来维护PCB队列的正常运行,通常情况下一个cpu只有一个运行队列,那么这里我们的上述图像就长这样:
在这里插入图片描述

让进程入队列本质上就是将进程的task_struct结构体对象放入cpu的运行队列里面,而不是进程的代码数据在cpu的运行队列里面排队,这就好比找工作投简历一样简历代表一位工作者,公司招的人很少但是需要这个岗位的人却很多,所以公司在招聘的时候会让这些人先投简历再面试等领导看到你简历写的不错人挺好时再发邮箱或者短信告诉你前来公司面试,而不是让所有需要这个岗位的人全部来公司一个个面试没有面试到的人就在公司的走廊里面一直等着,那么这里的简历就是进程中的PCB每一个需要这个岗位的人就相当于加载进内存的可执行程序。所以一旦某个进程的PCB(task_struct结构体对象)进入了cpu的运行队列里面的时我们就称这个进程的状态为运行状态,cpu的运行速度非常的快每个进程都要保证被执行的状态,所以在运行队列中的进程都被称为运行状态不管它是否真正被cpu执行。所以我们把这种状态称为运行状态简称R。

阻塞状态

通过之前的学习我们知道外设的运行速度相对于cpu来说是非常慢的,但是运行队列中的进程或多或少会去访问不同的外设,但是外设的数目也比较少并且一个外设也只维护一个属于它的运行队列,而且这个队列里面也会有多个程序正在等待,比如说使用浏览器学习的时候打开了qq和网易云音乐,那么浏览器肯定得访问网卡这个外设,而qq和网易云音乐都得访问网卡这个外设,外设的运行队列中也有很多的程序等待被执行,那么这里就会存在一个问题cpu的速度非常快而外设的速度很慢,当cpu执行一个程序发现这个程序要访问外设而该外设要正在忙其他程序时,cpu会一直等它忙完再把那个程序执行完吗然后cpu再接着执行该程序的下面内容吗?很明显不是的因为这样做的效率太低了,操作系统采用的是这种做法:下面的图中红色cpu队列中的红色圆圈表示需要访问磁盘的进程,黑色圆圈表示无需访问外设的圆圈,而磁盘中的黑色圆圈都是需要访问硬盘的
在这里插入图片描述

当一个进程要访问外设时操作系统会将该进程的PCB从CPU运行队列放到外设的等待队列里面进行等待,然后cpu再去执行其他的程序
在这里插入图片描述
等磁盘将前面的进程都执行完之后就轮到了红色的PCB

在这里插入图片描述

当磁盘的等待队列轮到红色圆圈的进程时,操作系统就会将红色圆圈放到cpu的等待队列里面去,并且将该进程的状态改为R状态,并且磁盘这个时候不会执行后面程序
在这里插入图片描述
等cpu再次执行到红色的PCB进程时就可以直接访问外设无需等待,那么我们将上述PCB位置发生变化的过程称之为进程的阻塞状态。所谓的进程的不同状态本质就是在不同的队列里面占用或等待某种资源
在这里插入图片描述

挂起状态

我们上面说过当一个进程被cpu执行的过程中要访问其他外设的时候操作系统会将该进程的PCB放到外设的等待队列里面然后cpu再执行其他的进程代码等外设执行到这个进程时,再把该进程的PCB返回cpu的进程队列,可是这里就会出现一个现象,内存中会有很多进程而且会存在不少的进程都要访问外设,而外设的速度又十分的慢,所以就会导致大量的进程的堆积,一个程序被加载进内存时程序里面的内容和数据会占用空间,操作系统为这个程序创建的PCB也会占用空间,这些进程既不会马上被外设所处理,又不会被cpu所执行,还要占用很多内存空间,及时外设准备好了等待的程序也不会立即被调度,它也要等待cpu的运行队列执行完,所以一旦内存的空间不够用了怎么办,那这时有些小伙伴就会说啊,这还不好办当内存的空间不够用了就不再加载任何可执行程序进入内存不就可以了吗?如果操作系统这样做的话就会导致机器整体的性能较低一旦内存满了你就只能在电脑面前等着不能做任何操作就安安静静的等着cpu将那些大爷伺候完,所以这么肯定是不妥的,所以操作系统就采取这样的方法来应对内存不足的情况,内存中有很多的进程中不是有很多进程既不会马上被cpu执行还要等慢吞吞的外设,这里等待东西是进程的PCB而我们知道一个进程是由程序的代码数据和PCB组成的,所以当内存不够的时候操作系统就会将那些等待的进程的代码展示存到磁盘中的一个指定位置,这样就可以节省一部分空间出来给其他的被加载进来的程序,等内存有空间了或者马上要调度你执行你了,操作系统就会将这个进程所对应的代码和数据再加载回内存执行对应任务,那么我们把进程的相关数据加载或者保存到磁盘上的过程叫做内存数据的换入换出,当一个进程的代码被放入磁盘但是PCB却还在内存的话我们就称这个进程的状态为挂起,这里大家要注意的一点就是一个进程如果处于阻塞状态的话,他不一定会挂起,但是如果一个进程处于挂起状态的话那么他一定是阻塞。

Linux的状态

有了前面三个状态作为基础我们就可以来理解linux操作系统里面的进程状态,首先来看看linux系统中有哪些进程状态:

static const char * const task_state_array[] = 
{
	"R (running)", /* 0 */
	"S (sleeping)", /* 1 */
	"D (disk sleep)", /* 2 */
	"T (stopped)", /* 4 */
	"t (tracing stop)", /* 8 */
	"X (dead)", /* 16 */
	"Z (zombie)", /* 32 */
};

第一个R就是我们上面所说的运行状态,S是浅度睡眠状态,D是深度睡眠状态,T是暂停状态,t是追踪停止状态,X是死亡状态,Z是僵尸状态,在linux这些状态就用后面的数字来进行表示,接下来我们就来看看这些状态分别代表什么意思。

浅度睡眠

首先大家来看看下面这段代码

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

这里通过循环语句将一段内容不停的打印到屏幕上面,但是屏幕属于外设,外设的计算速度非常的慢,而cpu计算速度却非常的快,所以屏幕可能得花很长到时间才能将cpu很短时间显示的内容全部显示完,所以我们看到这个进程的状态就一直是s,
在这里插入图片描述

我们把这里的s称为是浅度睡眠状态它是阻塞状态的一种特殊类型。

深度睡眠

首先我们来看看下面的图片,内存中有一个进程 A ,进程 A 要干一件事情就是往磁盘中写入一些重要的数据,当进程 A 往磁盘中写入数据的时候很可能会被挂起,但是也有可能会因为内存严重不足导致进程 A 被杀死,而我们知道往磁盘中写入数据大概率是会成功的,但是也存在小概率往磁盘中写入数据失败,所以这时就会出现这么个情况 PCB 在等外设磁盘将重要的数据写入,可是此时的内存已经严重不足了,通过挂起的方式也无法缓解内存不足,这时操作系统看到进程 A 的 PCB 真正悠闲的等待外设磁盘写入一下子就将该进程的 PCB 给杀死了,这时磁盘的写入发生了错误,可是就当驱动准备将数据返还给 PCB 时发现 PCB 消失了,这时的数据不知道该如何返回就会导致数据丢失,那么大家看了这个故事觉得数据的丢失是谁的错误呀,是磁盘的错误吗?可是磁盘的写入写出本来就会存在崩溃错误的存在磁盘是没错的,那是进程A的错误吗?进程A说怎么就是我的错呢?我是最惨好吧我是被杀的啊,那是操作系统的错吗?可是操作系统的目的是为用户提供一个良好的安全的高效的使用环境啊,内存都快满了进程 A 的 PCB 却依然在哪里喝着酒唱着歌的无所事事,为了服务客户我只能杀他啊,大家这么一说感觉谁都没错但是这件事的结果却是错的,所以为了解决这个问题操作系统就提供了一个深度随眠状态,这个状态的目的就是防止正在等待的进程被杀死,处于该状态的进程无法被操作系统杀死,只能通过断电或者进程自己醒来的方式来解决。

暂停状态

kill -l 可以查看进程中的很多信号,其中就可以看到我们之前使用的让一个进程死亡的9号选项
在这里插入图片描述

当一个进程运行的时候我们可以使用指令kill -19 id就可以将对应id的进程暂停,这时进程的状态就会由R状态变成T状态,比如说下面的代码:

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

这段代码没用访问外设所以这个进程运行起来是R状态:
在这里插入图片描述
输入指令kill -19 5519然后再检查状态就可以看到进程myproc的状态变为了T
在这里插入图片描述
如果想要这个进程从暂停状态变成开始状态的话就可以使用指令kill -18 id这样就可以使一个进程从暂停状态变成运行状态,
在这里插入图片描述
这里观察仔细的小伙伴们可能就会发现此时的继承虽然是运行状态但是它由R+变成了R状态,那么这就说明该进程由前台进程变成了后台进程,对于前台进程我们可以使用ctrl c来终止其运行,比如说下面的代码:

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

这段代码运行起来是可以通过ctrl c终止的:
在这里插入图片描述
但是对于后台进程我们就只能通过kill 9 id 指令的形式讲起终止,比如说下面的操作:
在这里插入图片描述
然后再使用kill -19将其再开始运行的话就可以发现输入ctrl c就无法将终止了
在这里插入图片描述
那么这里大家要注意一下,其次一个进程暂停之后是处于阻塞状态还是挂起状态我们是不知道的操作系统也不会显示,这里怎么做都由操作系统决定的,阻塞和挂起都是我们所说的暂停状态,但是阻塞后有没有挂起就只由操作系统决定了,我们只用知道这个程序还在内存中只是没有被执行就可以了。

追踪停止状态

当我们使用gdp调试一个进程的时候,该进程就会处于追踪停止状态。也就是说程序当前停止了运行但也不是完全停止,该程序是否执行下一步指令取决于你是否给他指令,比如说下面的操作:首先将makefile文件进行修改让该文件以debug的形式运行
在这里插入图片描述
然后我们再使用gdb来调试这个进程,并查看这个进程的状态就可以发现此时的进程处于t状态:
在这里插入图片描述

僵尸状态

一个进程被创建出来是为了完成某些任务,既然是完成任务的话我们就要知道这个任务完成得结果如何?所以进程在退出得时候不会立即释放该进程对应的资源,而是得保存一段时间等父进程查看完子进程的运行结果之后再释放该进程对应的资源和PCB,这就好比警察在路边发现一个尸体之后并不是里面将这个尸体火化并埋起来,而是对这个尸体进行一些列的检查查询他死亡的原因,确定不是他人杀死之后再通知其家属将这个尸体进行火化并埋起来,所以当一个子进程运行结束之后如果父进程不对其进行处理的话这个进程就会变成僵尸进程,该进程只有等父进程结束然后才能让操作系统对其进行回收。一个进程在运行结束的时候对应的代码和数据会被释放,但是要是不对其处理的话该进程对应的PCB不一定会被释放,所以一个进程要是变成了僵尸进程,那么它的PCB就会一直在内存里面从而导致内存泄漏,我们来看看下面的代码:

  1 #include<stdio.h>  
  2 #include<unistd.h>  
  3 int main()  
  4 {  
  5     pid_t id=fork();
  6     if(id==0)
  7     {
  8         printf("我是子进程我的pid为%d,ppid为:%d\n",getpid(),getppid());
  9         sleep(10);
 10     }
 11     else 
 12     {
 13         while(1)
 14         {
 15         printf("我是父进程我的pid为%d,ppid为:%d\n",getpid(),getppid());      
 16         }
 17     }
 18     return 0;
 19 }    

我们将进程运行起来就可以看到一开始子进程的状态为:
在这里插入图片描述

在这里插入图片描述
但是过了10秒子进程运行结束之后就可以看到子进程的状态变成了Z僵尸状态
在这里插入图片描述

死亡状态

这个状态我们是看不到的,因为不管操作系统有多少延迟,对于操作系统来说都是非常快的,进程一旦死亡了就会立马被删除所以我们看不到进程显示死亡状态。

孤儿进程

当子进程结束父进程不对其进行回收的话子进程会变成僵尸进程从而导致内存泄漏,如果子进程一直在运行可是父进程已经结束的话,那么子进程就会变成孤儿进程,这时子进程的父进程就是操作系统,操作系统是pid为1,系统之所以这么干就是因为父进程退出的时候并没有回收子进程的运行结果,那么这时子进程再退出的话就会变成僵尸进程,这时便没有人可以回收子进程的PCB,所以这时操作系统就会领养子进程,在子进程结束的时候回收它的PCB以防内存泄漏,但是当一个进程变成孤儿进程之后该进程也会从前台进程变成后台进程,那么这就是本篇文章的全部内容希望大家能够理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶超凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值