【Linux】-进程概念及进程状态(僵尸进程和孤儿进程)

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!


前言

上篇我们刚刚讲解完毕task_struct里面的第一个内容标识符,也讲解了fork的具体作用和用途,今天这篇我在带大家来学习task_struct里面另一个属性-进程状态,这个也是非常重要的,由于内容比较多,所以我选择单独一篇博客给大家介绍,希望大家可以更好的理解,此篇主要是概念,所以文字相对较多,代码相对较少,也希望各位读者可以耐心的阅读下去,话不多说,我们开始进入正文


task_ struct内容分类

标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据。
I/O状态信息: 包括显示的I/O请求分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息

一、初步认识三种状态:

我们先从一般的操作系统学科来介绍三种常见的状态:运行,阻塞,挂起
还有其他状态都不是特别的重要,然后我们再来学习Linux上的状态是什么样的,我们在网上找一些操作系统状态图
在这里插入图片描述

相信大家对这些名词几乎不了解,可能也没有听说过,接下来我就给大家进行解释

1.1运行态

在这里插入图片描述

通过这个图相信大家已经理解了什么是运行态了吧

1.2阻塞态

这个状态不太好理解,还是需要画图来能给大家去介绍的
在这里插入图片描述

阻塞态是进程和外设之间的资源请求,通过外设输入数据让进程可以继续运行下去,相信大家应该了解了进程的阻塞态了吧

1.3挂起态

挂起态可以说是在阻塞态的基础上产生的状态
在这里插入图片描述

相信大家此时对挂起态有了一定的了解了

通过上面的三种进程的状态的了解,我们来看看Linux上是怎么处理他上面进程的状态

二、Linux上的状态

在这里插入图片描述
我们有这么多状态,博主都会一一介绍的,大家不用担心。

2.1R状态

Linux的R状态就是运行状态和我上面介绍的运行态是一样的道理,在运行队列里面就是运行态
我们来看一段代码:

#include<stdio.h>
int main()
{
    while(1)
    {
        printf("我是一个进程\n");
    }
    return 0;
}

#include<stdio.h>
int main()
{
    while(1);
    return 0;
}

在这里插入图片描述

第一个程序和第二个程序都是写了一个死循环,而且都是没有在终止进程的时候去查看的,为什么第一个程序是S状态,而第二个程序是R状态首先S状态先理解为阻塞状态,我们的cpu运行太快了,因为第一个程序是一个死循环,所以肯定不止在cpu上就跑一次,他应该一直从cpu被拿下被放上,在运行队列里面待着,但是他每次放下来需要去打印,由于打印是和外设打交道,而外设太慢了,所以不能一直放在运行队列中等待排队,万一又到我这个程序开始运行了,但我还在往外设的显示器是慢慢打印呢,这时候是不是就没有准备好,所以就不能放在运行队列上,此时应该在等待队列上等待打印,等打印好了,又会被放到运行队列,在跑下一次,由于速度都很快,我们观察到的结果就是一直在运行的状态给我们一定的错觉,大部分时间都在等待队列待着等待打印,所以我们大概率看到都是S状态那第二个程序呢?他由于没有与外设打交道,所以他是一直在运行队列,不停的拿上拿下,但始终在运行队列中,所以看到就是R状态

我们看到状态后面的+号是什么?
他标识前台运行,意味我们其他程序跑不了,因为他一直在运行,我们命令都输入不了,我们可以在运行的时候加一个&让他变成后台运行./myproc &
在这里插入图片描述
此时就是后台运行,由于后台运行没有办法使用CTRL+c结束程序,所以我们必须使用这个命令把程序杀死kill -9 PID,-9是一个信号,一会还会介绍到其他两个信号。
在这里插入图片描述
相信大家对Linux上的R状态(运行状态)应该有所了解了吧

2.2S状态

我们在R状态提到过,S状态,我们可以暂时理解为阻塞态,在Linux上我们理解为睡眠状态:意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。可中断请求就是可以随时被唤醒,介绍R状态的时候我们往显示器去打印,就是等待打印任务完成,当正在打印的时候,就是睡眠状态,打印好了就被唤醒了。

我们来看案例:

#include<stdio.h>
int main()
{
     int n=0;
     scanf("%d",&n);                             
     return 0;
}

在这里插入图片描述

我们等待键盘输入的时候就是在等待事件的完成,所以是处于S状态.


我们在来看看我们的bash程序:
在这里插入图片描述

大小S不用管都是属于睡眠状态,我们的bash就是命令行解释器,等待我们输入命令,就是等待事件的完成,所以也是阻塞态,只要是和外设有交流的进程都可以理解为睡眠状态(只包括浅度睡眠),不止是键盘和显示器。

我们大部分看到的进程都是处于阻塞态,为了显示效果,写特殊的代码让大家看到其他状态。相信到这个时候大家对Linux上的S状态(浅度睡眠状态)应该了解了吧。

2.3D状态

这个状态和睡眠状态很像,上面叫浅度睡眠,可以随时被唤醒,而D状态他叫深度睡眠,不可中断的睡眠状态,不会相应任何人的命令,这个状态不太好展示的,有可能造成数据丢失,所以我将会以一个小故事的形式来给大家进行展示:

小故事时间:

D状态也叫睡眠状态,他的操作是等待IO时间完成,这时候我们的进程假设要将1GB的数据放到磁盘里面,磁盘跟进程说,你这个1GB数据不一定能放的下,放不放的下我到时候到来跟你说一声,万一放不下去,我在把已经放下数据在还给你,你先等我一下,我速度很慢的,此时的进程就翘着二郎腿在等啊等啊,此时操作系统路过了,他正在清理占用资源的进程,此时发现这个进程有没有在使用,说你等啥等呢,浪费我内存的资源,直接给这个进程清理掉了,此时硬盘放着放着发现放不下,就不好意思的回来告诉进程,可进程去不见了,硬盘就不知道怎么办了,硬盘心想我不知道还给谁啊,但别的进程还有放数据进来啊,不能占用资源啊,直接给丢掉了,万一这是一个非常重要的银行转账记录,那损失可就大了。


此时就来了一法官,他要追究到底是谁的责任啊
操作系统辩论:法官大人,这可不能怪我啊,我们的本质工作就是维护进程,管理内存空间,他占用内存资源,导致内存严重不足,数据丢失和我没有关系,我只管合理分配内存空间给进程使用,如果不清理他,导致其他进程崩溃,那样损失更大,该丢的数据还是得丢失
磁盘辩论:法官大人,这事和我也没有关系,我放的时候就已经和进程说了可能放不进去,让进程等待我一下,我就是一个打工人,别人说什么就是什么可最后他人却不见了,我也不知道把数据放在哪里,其他进程还要放数据呢?不能耽误别人,我没有办法只有丢掉
进程辩论: 法官大人,我才是受害者啊,我在等待磁盘给我回复的过程中,操作系统直接来二话不说给我带走了,这不是自己的行为,我i也没办法啊


法官大人听了三个人的辩词说的都有道理,也不知道判定是谁的问题了,好心的法官就自己承担了此次损失,好在法官是程序员,进程和磁盘工作好好的,是操作系统路过把两人直接的交流打断的,索性法官直接修改操作系统,给此进程一个免死金牌,任何响应都不能影响此进程和磁盘直接的交流,操作系统也不行,所以此时的进程就处于深度睡眠状态,没有人可以中断他,也叫D状态

现在都是固态硬盘速度也是比较快的了,所以进程不需要怎么等待硬盘的回复,所以也很少出现D状态,偶尔出现一个就不得了,出现多了那么你的操作系统肯定就会挂掉。所以D状态我们知道是什么就行了,我就不给大家验证了,博主怕自己的linux操作系统崩溃掉😂,相信到这里大家对D状态(深度睡眠状态)已经有所理解了吧

2.4T和t状态

这两个目前没有啥区别,我们当成一个状态就行了

T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
我们来看案例:

 #include<stdio.h>  
 int main()  
 {
       while(1)
       {
           printf("我一会就要停止运行啦\n");       
       }  
       return 0;
  }  

在这里插入图片描述
9号信号我们刚才已经使用过了,是杀掉进程的,19号信号是停止程序,18号信号是恢复程序。

停止: kill -19 PID
在这里插入图片描述
恢复: kill -18 PID
在这里插入图片描述
我们看到我们恢复之后就默认变成后台运行,为啥看到是S这里我就不多说了。


我们最常见停止程序是什么??gdb 他不打断点r是直接运行结束,打了断点会在断点处停止,我们需要将我们的程序加-g选项,才能调试
我们来看测试:
在这里插入图片描述

所以我们的断点里面肯定也有一些关于停止信号的命令,导致程序可以被暂停。相信到这里大家对停止状态已经有所了解了吧

2.5Z和X状态

这两个状态是有一个先后关系的,先是X状态然后到X状态,先来介绍一下神叫做Z状态

Z(zombie)-僵尸进程:

  1. 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
  2. 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  3. 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

大家肯定不理解,此时又到 小故事时间

有一天你悠闲的在路上小跑,突然有一个人飞速的跑过去,然后在你前方两百米的地方突然躺平了,你跑过去处于好心,拨打了119和110,此时119快速的来到了现场,然后发现这俄格人救不活了,然后就撤了,此时110来了,他第一件事情不是直接清理现场,而是要保护现场,找法医来看看死者的死因是什么,然后拉起禁戒线,等待家属来,这段时间这个人会一直在这躺着。此时就好比进程一直处于僵尸状态。


那么到底谁最关心你的死活,是不是就是家属,在Linux中就是父进程,他最关系你了,他需要把你的尸体清理带走,在Linux中,父进程就要清理子进程退出后的代码,如果父进程一个不清理读取的话,那么此时的子进程就一直会在哪没人清理,处于僵尸状态。

所以这个时间大家对僵尸进程是不是有所了解了,就是子进程推出后,父进程一直不读取他的退出码,他就一直清理不掉,处于僵死状态,所以我们需要使用fork来创建父子进程,来测试我们想要的效果:

  1 #include<stdio.h>                                                                  
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7     pid_t id=fork();
  8     if(id==0)
  9     {
 10         int cnt=5;
 11         while(cnt)
 12         {
 13             printf("我是子进程,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt--);
 14             sleep(1);
 15         }
 16         exit(0);//主动让进程退出,而不是程序退出了他在退出,模拟父进程在运行没有时间清理父进程
 17     }
 18     else if(id>0)
 19     {
 20 
 21         while(1)
 22         {
 23             printf("我是父进程:pid:%d,ppid:%d\n",getpid(),getppid());
 24             sleep(1);
 25 
 26         }
 27     }
 28     return 0;
 29 }

使用此命令监视进程状态:while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep ; sleep 1 ; done

在这里插入图片描述
我们发现子进程退出之后父进程在忙着运行自己的程序还没有读取到子进程的退出码,所以子进程一直是处于僵尸状态,这个单词defunct这就是死的意思。

显然僵尸进程是不好的,所以来说说僵尸进程有哪些危害:

  1. 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
  2. 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
  3. 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
  4. 内存泄漏?是的!
  5. 如何避免,后面介绍

说完Z进程,我们来看看X进程:这个状态只是一个返回状态,你不会在任务列表里看到这个状态,他是一个瞬时的状态。


接下来我们再来介绍一下另一个进程-孤儿进程
上面的僵尸进程是子进程先退出,父进程没有及时去清理,而孤儿进程是反的,父进程退出了,子进程到最后没人清理,就变成孤儿了,遇到这种情况,我们看看操作系统是怎么处理的

  1#include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7     pid_t id=fork();
  8     if(id>0)
  9     {
 10         int cnt=5;
 11         while(cnt)
 12         {
 13             printf("我是父进程,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt--);
 14             sleep(1);
 15         }
 16         
 17         exit(0);
 18     }
 19     else if(id==0)
 20     {
 21 
 22         int cnt=15;
 23         while(cnt)
 24         {
 25             printf("我是子进程:pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt--); 
 26             sleep(1);
 27         }
 28 
 29     }
 30     return 0;
 31 }

在这里插入图片描述
通过上面的示例我们发现,我们的父进程退出之后,我们的子进程的父进程就变成1,我们来看看PID是1的进程是哪个程序:
在这里插入图片描述
这个1号进程正是我们的操作系统的进程,我们的操作系统领养了我们的子进程,
为什么要领养呢?为了方便子进程退出,有人给他清理资源
为什么bash补领养呢?因为bash只负责自己的子进程,没有能力领养。
相信此时大家对孤儿进程应该了解了吧

至此,值得关注的进程状态全部讲解完成

三、总结

到这里我们关于Linux上的进程都讲解完毕了,不知道同学们有没有收获,概念性比较多,但理解了就简单了,博主使用生动的小故事就是为了让大家可以更好的理解进程状态,希望大家可以可以都偶都支持博主,下篇我们开始介绍进程的优先级,也是一个非常关键的属性,希望大家到时候来支持博主,我们下篇再见

请添加图片描述

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘柚!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值