在这里插入图片描述
在这里插入图片描述

本篇博客全站热榜最高排名:6
本地图片
本地图片

一、前言

Hello,大家好,本文我们所要介绍的是有关Linux下的进程状态

  • 在上一文中,我们重点介绍了有关 Linux下进程的基本概念,了解了什么是进程、怎么去描述并组织进程、创建一个进程。
  • 在本文中,我们将先通过了解操作系统学科下的进程状态,对进程的状态有一个基本的概念,然后呢再去学习Linux下的7种进程状态,学习这PCB结构体中的第二个成员变量

task_ struct内容分类

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

二、操作系统学科下的进程状态

  • 对于进程而言呢,它是操作系统中的概念,如果有学习过《操作系统》这门学科的话应该可以很清楚对于进程而言的话是存在着许许多多的状态,如果一开始刚刚接触的小伙伴一定会感觉这么多状态要怎么区分呀😵

在这里插入图片描述
其实那么多的状态,真正主要的也就那么几个,所以接下去我会中重点讲解以下几种进程的状态

1、运行状态

首先我们要谈到的是【运行状态】,这个状态是最普遍的

  • 首先对于一个进程而言,我们知道它是由 内核数据结构 + 所对应的代码和数据 所组成的,所以当系统中存在多个进程的时候就势必会存在多个结构体;当然我们需要将这些进程给链接组织起来
  • 那么这些进程就相当于是在处在一个运行队列中,我们如果要找到这个队列中的某个进程的话,只需要找到这个进程的头部即可,那我们就可以对应地去调度某个进程,把这个进程所对应的代码和数据放到CPU上去执行

💬 因为每个进程是需要去竞争CPU资源的,但是呢CPU不可能同时给这么多进程分配资源

  • 所以每一个CPU都会去维护一个运行队列,里面的队头指针head所指向就是第一个进程所对应的【task_struct】,队尾指针tail所指向就是最后一个所对应的【task_struct】。所以我们要运行某一个进程只需要将 head 所指向的那个进程放到CPU上去运行即可

在这里插入图片描述

  • 但是CPU无法直接去找到某个需要调度的进程来运行,此时呢就需要一种东西叫做【调度器】,CPU通过找到运行队列,运行队列找到调度器,然后调度器再去一一调度所需要运行的进程,那么此时所被调度的处于运行队列里的这些进程所处的状态我们称之为 运行状态R

在这里插入图片描述

提问:一个进程只要把自己放到CPU上开始运行了,是不是一直要到执行完毕,才把自己放下来?

  • 不是,每一个进程都有一个叫做:时间片的概念! 其时间大概是在10 ms左右。所以并不是一个进程一直在执行,而是这多个进程在一个时间段内所有代码都会被执行 —— 这就叫做【并发执行】
  • 所以呢这就一定会存在大量的进程被CPU放上去、拿下来的动作 —— 这就叫做【进程切换】

💬 所以呢我们不要拿自己的时间感受去衡量CPU,其运行一遍速度是非常快的,你根本感受不到这种进程切换的效果

2、阻塞状态

在介绍完【运行状态】后,我们再来讲讲【阻塞状态】

  • 对于阻塞状态而言,其实就是 进程等待某种资源就绪的过程,例如你当前的这个进程向操作系统发起了IO请求,那么当前这个进程就会处于暂停状态,无法继续运行,但是这样看还是太比较难理解,举两个例子来说明一下
    1. 如果你的网络突然断了,进程无法继续进行下载的任务,此时它只能靠边,CPU便去跑其他进程了,等这个进程就绪了才继续来跑这个进程
    2. 去银行的柜台办存钱,但到了你的时候突然发现单子还没有打印好,于是只能在一旁进行等待

💬 上面的这两种状态都可以算作是进程处于阻塞状态

  • 之前我们在讲 操作系统基本概念 的时候,有说到过操作系统要去管理底层的硬件的话需要进行【先描述,再组织】的动作,那就是将这一个个生冷的硬件抽象成为结构体类型,内部的type代表这是一个什么类型的硬件、status代表这个硬件当前在操作系统中所处的状态是什么样的,wait_queue代表的则是等待队列的指针

在这里插入图片描述

  • 那我们继续去谈起操作系统中的【进程】,现在呢有一个进程想要去读取键盘中的数据,那么它就需要与我们刚才所描述的结构体相互勾连起来,因为操作系统根据这个结构体顺着体系结构去访问到最底层的硬件需要实现,所以这个进程便需要一直处于等待状态,我们可以将其链入【等待队列】中,即我们刚才所讲的wait_queue

💬 那有同学可能会疑惑这为什么叫做【等待队列】呢?明显只有一个进程鸭🦆

  • 一个进程当然不能算是队列,若此时又有一个进程也要来读取键盘中数据的话,我们就需要将其链入到这个等待队列中。此时这两个进程所处的状态即为 阻塞状态

在这里插入图片描述

  • 那么当键盘里一旦有数据了,我们只需要把这个进程放到我们在上面所讲的【运行队列】中即可,那么CPU在调度的时候就可以自动地到底层设备里去读取了;如果当键盘里没有输入的时候,最终我们的进程就只能在每一个各自的等待队列里去等待,把这种处于等待队列的进程称之为 阻塞状态

在这里插入图片描述

3、挂起状态

最后我们再来讲讲一种状态叫做【挂起状态】

  • 对于操作系统而言,我们知道它要为当前的所有进程分配内存资源,在上面我只画了一个等待队列,而且只有两个进程,若是一个等待队列中存在多个进程,并且通过有多个等待队列在排队的话,操作系统中的内存资源就会出现【消耗殆尽】的问题
  • 那么操作系统就得保证在正常情况下省出来内存资源

在这里插入图片描述

要怎么去省呢?

  • 只要一个进程没有被调度(运行),那么当前这个进程的 代码和数据 就是空闲的、没有被使用的。于是操作系统就想办法把这个进程内核的PCB给保留,然后将其 代码和数据 重新交换到外设当中【换出】,那么此时只有一个PCB在这里排队,我们把上面这种状态就称之为 挂起状态

在这里插入图片描述

  • 当这个进程就绪了,把这个进程放到运行队列时,此时再考虑把 代码和数据 重新放回进程中【换入】

💬 那有的同学说:为什么要这样去做呢?这样做有什么意义?

  • 答:当一个进程所对应的代码和数据交换到外设上时,操作系统内部就腾出了相应的空间,此时OS就可以将这块多出来的空间重新给到其他需要的进程使用,从而达到了循环利用系统资源的目的,节省了系统开销。

看了上面的三种基本的进程状态后我们可以来总结一下,如果要看进程是什么状态的话一般看这个 进程在哪里排队

  • 位于【运行队列】的话它就是处于运行状态
  • 位于【阻塞队列】的话它就是处于阻塞状态

三、Linux下的7种进程状态

在介绍完操作系统学科下的三种最主要进程状态后,我们对进程的状态有了基本的概念,接下去就让我们正式地来学习一下Linux系统下7种进程状态

先来小结并回顾一下上面所学:

  1. 如果当前是【运行状态】,那么接下来就需要被调度运行
  2. 如果当前是【阻塞状态】,那就等条件就绪,等设备准备好就把当前进程投递到运行队列里,然后再被CPU调度运行
  3. 如果当前是【挂起状态】,要做的就是把当时换出的代码和数据重新换入,然后再把所对应的进程列入到运行队列中

以下就是关于进程的所有状态

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 */
};

1、运行状态R

首先我们要来聊的是【运行状态R】

  • 来看下下面的这段代码,是一个死循环去printf打印循环语句
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main(void)
  5 {
  6     while(1);                                                                
  7     {
  8         printf("hello bit\n");
  9     }
 10 
 11     return 0;
 12 }
  • 然后我们将下面的代码给运行起来,观察这个进程的状态时,看到其显示为S+,不过呢读者想看到的应该是R才对

在这里插入图片描述

  • 接下去呢,我们把代码修改一下再来看看,不使用printf打印语句了,而且直接使用一个while(1)去做循环
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main(void)
  5 {
  6     while(1);                                                                   
  7     //{
  8     //    printf("hello bit\n");
  9     //}
 10 
 11     return 0;
 12 }
  • 然后我们看到,此时再运行起来时这个进程的状态就改变了,变成了R+,这才是我们想要的【进程状态】

在这里插入图片描述
💬 那有读者就要问了:为什么把printf打印语句给去掉之后就变成这样了呢?

  • 原因就在于printf打印语句它是属于IO流的一种,第一次因为是循环的缘故,它一直在等IO设备就绪,所以其进程状态就一直为S+,对应的即是在操作系统中的【阻塞状态】;但是当我们去掉printf这种IO流之后呢,它就是在纯纯运行,没有IO,那也就变成了R状态

这里再补充说明一下这个SR后面的+

  • 这里的R+代表的就是这个进程是在前台运行的,所以我们在输入任何指令后不会对其造成 任何的影响

在这里插入图片描述

  • 那若是我们不以正常的方式去启动这个进程的话,其进程的状态就会不一样了,可以看到我在./myproc的后面加上了一个&;那么其状态变成了R,此代表的意思就是这个进程它是运行在了【后台】的

在这里插入图片描述

不过呢,R状态并不代表这个进程就在运行,而代表其在运行队列中排队而已

2、浅度睡眠状态S

接下去我们再来介绍一下Linux下的睡眠状态S

  • 下面我们要运行的代码是每隔一秒打印一句内容
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main(void)
  5 {
  6     while(1)
  7     {
  8         sleep(1);                                                                   
  9         printf("hello bit\n");
 10     }
 11 
 12     return 0;
 13 }
  • 运行起来后可以看到这个进程所处的状态即为S+睡眠状态

在这里插入图片描述
💬 那有同学就很疑惑,这个进程不是在运行吗?为什么不是R状态呢?

  • 我们可以通过动图来观察看看,每一次去查看这个进程的状态时,都是处于S+这个睡眠状态。其实呢,这和外设与CPU的速度之差是有关系的,CPU那个运行速度是极其快速的
  • 虽然我们肉眼可见右侧的进程是一直在每隔一秒运行,但是呢在我们查进程状态的时候看到的却只能是S+;因为有99.99%的时间都在进行等待,而只有0.01%的时间在运行,它们之间简直可以说是数量级别的差别

在这里插入图片描述

  • 不过呢,上面这样还无法看出一个进程是否真正地处于睡眠状态,我们再通过一段代码来看看
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main(void)
  5 {
  6     int a = 0;
  7     printf("Enter# ");
  8     scanf("%d", &a);
  9 
 10     printf("echo : %d\n", a);
 11     return 0;                                                                         
 12 } 
  • 将该进程运行起来我们可以看到其是出于S+的状态,因为【shell】此时正在等待用户的输入,这个就对应到了我们上面所讲到的 阻塞状态

在这里插入图片描述

在这里插入图片描述

  • 不仅如此,像我们一直在使用的bash,也可以算是一种【阻塞状态】,一直等待着我们去输入命令行,一旦有了的话它就进行读取

在这里插入图片描述

在这里插入图片描述

3、深度睡眠状态D

除了【浅度睡眠】之外呢,还有一种叫做【深度睡眠】,它们俩呢,都是 阻塞状态

  • 对于浅度睡眠来说,之所以称为 “浅度”,是有原因的:也就是处于这种状态的进程容易被唤醒。例如说我们在上面所讲到的这个处于阻塞状态的进程,我们使用kill -9 8664向这个进程发送【9号信号】,那么这个进程就被杀死了,你也可以认为被唤醒了

在这里插入图片描述

好,接下去呢我就通过一个故事📖来描述一下这个【深度睡眠】到底是怎样一种状态

  • 现在在操作系统里有一个进程,它呢想要把数据写到磁盘上去,磁盘这个时候听到了它的请求,就开始写数据,不过我们知道外设尤其是像磁盘这种设备的速度都是比较慢的,所以呢这个时候进程就会一直地等待下去

在这里插入图片描述

  • 但是呢,光这么无休止地等待下去可不行。因为当前这个时候操作系统中的已经有非常多的进程了,那么OS它看不下去了,路过的时候看到当前的这个进程正处于等待状态,也不运行,于是二话不说把它终止了

在这里插入图片描述

💬 所以呢,同学们,我们要明白这么一个道理:操作系统认为当前系统能行的话就直接让用户去用就可以了,如果扛不住了就置换页表,实在不行了就去会杀进程🔪

  • 那么当这个进程被杀掉的时候,磁盘说:我在写数据的时候出了问题。它这个时候就回去找那个调度它的进程,但是发现这个进程没了???所以磁盘就特别尴尬:我要怎么办呢?写好的这个数据该不该还放在这里【一般的设备都会选择丢失】
  • 如果这份数据是普通的那还好,但如果其为银行当中那种非常重要的数据呢?该怎么办?

一场有趣的官司

那因为随着这份重要数据的丢失呢,就引发了一场官司🔨

  • 到了法庭上,法官开始问话,审讯【操作系统】、【进程】、【磁盘】这三个嫌疑人,读者认为是谁的问题的?锅在谁呢?

在这里插入图片描述

以下呢,是三个人的辩词

💬 【操作系统】:

  • 请问有没有赋予我管理它软硬件资源的权利?请问我杀掉进程是不是在特别极端的情况下杀掉的?请问我有没有履行我操作系统的职责?我的职责是保证系统不挂?数据丢失和我有什么关系?
  • 我就是在做我操作系统该做的事,如果你判我有罪了,那么请问我下次再遇到类似的情况时,到底我还做不做,我如果不杀的话,最终导致操作系统挂掉:① 数据该丢还是得丢;② 可能还会影响其他进程。这些责任谁来承担呢?

法官👨‍⚖️听了这番说辞后心里想了想,确实是。于是呢这个时候便把茅头指向了丢掉数据的磁盘✒ 发问道:你为什么要丢数据呢?

💬 【磁盘】:

  • 法官大人啊,这你不能怪我╮(╯▽╰)╭ 在这件事情上我就是个跑腿的,人家让我干啥就干啥,我在写入的时候就已经告诉了对方我会失败,我让他去等的,我要给它结果的,我的工作模式向来都是这样,其他磁盘也是这样
  • 如果你认为我有罪的话,是不是其他的磁盘也有问题呢?我丢失也是有原因的,其他进程也要叫我写入数据,那我也去写了,并且汇报回去了,那其他磁盘怎么没出问题呢?

操作系统一听:诶,这货说的好像确实没什么问题。那就就把视角转向了受害人即【进程】这一方。反正操作系统没错、磁盘没错,那就是你的问题喽!

💬 【进程】:

  • 法官,我可是受害人啊/(ㄒoㄒ)/~~ 我就是静静地坐在那里,“人在家中坐,锅从天上来呀”。我是被杀掉的,我怎么能有错呢?

那这个时候法官一想,它们三个说的似乎都挺有道理,难道是我错了吗?

  • 其实上面三者都没错,操作系统履行了它的职责,在关键时刻杀掉了进程;而磁盘呢则是起到了它本能的义务,丢失了没人要的数据;进程呢则因为在等候期间无意中被操作系统给杀掉了
  • 当这个进程在等磁盘写入数据这么关键的时候,应该要让其受到保护,不可以被任何人给杀掉才对

那我们在上面有提到过处于【浅度睡眠】的进程,是可以被kill掉的,那么我们就要让这个进程的状态变为【深度睡眠】才对,即[D]

  • 那既然这个进程的状态被设置为D后,当它在等待这个磁盘写入的时候,操作系统路过,看到这个进程没有跑起来空等着,于是在想把他杀掉的时候就被这块 《秒死金牌》给吓到了😮

在这里插入图片描述

  • 那么这个进程就不会被杀死了,当磁盘写完数据后告知进程,那么它就可以将自己放入【运行队列】里去运行,此时它的状态就变成R

💬 那这个时候就又有同学问了:D状态这么强大吗,那如果一个操作系统里有很多的D状态,这怎么办呢?

  • 这个同学你问得很好,确实这个D状态的话操作系统是没有办法将其杀掉的,而是要等到磁盘写入完毕或者什么事情执行完毕后其才会去自动结束这个进程,或者是在外部断电、直接拔掉电源即可
  • 一般一个系统里面如果存在D状态的话,那这个系统中的磁盘压力已经非常大了;如果存在多个D状态的话,则表示这个系统已经是非常危险了,最好马上重装一下系统💻

不过呢这个[D]就没办法在这里给读者演示了,因为D状态的进程只有处于高IO的情况才可以演示。有兴趣的可以去研究一下Linux下的命令dd

4、停止状态T

好,接下去呢我们来讲讲【停止状态T】

  • 首先我们要通过下面这句命令来查看一下对应的进程信号
kill -l
  • 此处我们要使用到的是18、19号信号

在这里插入图片描述

  • 接下去我们就来试一试如何让这个进程暂停之后又重新启动会是怎样的

在这里插入图片描述

  • 所以我们来总结一下
    • 暂停进程
    kill -19 PID
    
    • 启动进程
    kill -18 PID
    

💬 所以呢,如果我们要将一个进程给终止的话,发送19号信号即可,要让其继续启动起来,则发起18号信号

那我现在要问了,这个T停止状态和S睡眠状态有什么不同呢?

  1. stopped状态进程 完全暂停了, 其不会再接收任何信号了
  2. 一个进程通过stopped状态可以控制另一个
  3. S和D一定是在等待某种资源,而T状态可能在等待某种资源,也可能被其他进程控制

5、进程跟踪状态t

接下去呢,我们再来说说进程的跟踪状态t,还记得我们在基础篇中所学习的 GDB调试 吗?

  • 首先我们在正常状态下查看这个进程的状态,其为S睡眠状态。接下去呢我进行了【gdb】调试,在行内打上断点后,输入r运行起来后,我们再去查看这个进程的状态时,就发现其状态变成了t原因就在于这个进程在调试的时候停了下来

在这里插入图片描述

6、死亡状态X

对于【死亡状态X】来说呢,这个状态只是一个返回状态,你不会在任务列表里看到这个状态🙅‍

  1. 第一种方法就是向这个进程发送9号信号,就可以杀掉这个进程
kill -9 PID

在这里插入图片描述

  1. 第二种方法就是通过这个进程的名称来杀掉它
killall 进程名

在这里插入图片描述

7、僵死状态Z —— 两个特殊进程

接下去我们来介绍一种状态叫做【僵死状态Z】,对于这个状态,我们要涉及到两个特殊的进程叫做 僵尸进程孤儿进程

💬 不过在讲这个僵死状态前,我们先来看看return 0这个东西

  • 对于这个相信读者在学习C语言的时候写main函数一定写过,但是呢为什么要返回这个0呢,你可能还不是很清楚,那现在我们学习到了Linux下的进程状态,就可以给大家来讲讲了,这个0就叫做【进程退出码】

这里我们写几句代码来测试一下

 36     int ret = 20;                                                                            
 37     if(ret == 20)   return 0;
 38     else    return 3;
  • 我们让这个进程运行起来然后通过echo $?去查看一下当前进程的退出码,发现没问题是0

在这里插入图片描述

  • 但此时若是我修改了初始值为30后再去运行的话,就发现这个进程退出码发生了变化

在这里插入图片描述

① 僵尸进程

💬 首先我们要来介绍的是僵尸进程,这里呢通过一个故事来进行引入

  • 你呢,很喜欢晨跑🏃‍,这一天早晨6点又起来跑步了,当你路过一个公园的时候,遇到了一个晨练的【程序员】,边跑边掉头发😀 但是呢,跑着跑着,此时突然他就“啪叽”倒了下来。那你此时就非常担惊受怕了,马上拨打了110120的电话,然后守在他的身边等待救援来到,周边的人看到也都纷纷围了过来,其中不免有一些人会紧急救援。不过等了一会这个人就没了呼吸🖤

在这里插入图片描述

  • 过了十几分钟后,救护车和警车都来了,警察先封锁了,让法医过来检验一下其状况,就说:“这个人已经没救了,赶紧通知家属准备后事吧~。”

在这里插入图片描述


好,我们回归正题,来说说这个【僵尸进程】

  • 因为在救护车来之前这个人其实就已经死亡了,但是其状态还没有被检测,当前并不知道它的死因,所以我们操作系统就可能会维护当前的这个状态,这个状态即为Z状态,即[僵死状态]

💬 那我现在想问了:有一个进程暂时退出了,它要将它的状态暂时维持一段时间,问题是它维持给谁看呢?

  • 答:父进程!当一个进程退出的时候,那最关心它的便是【父进程】。因为这个父进程费了很大的劲才将这个子进程fork出来,此时呢它突然挂掉了,那么此时父进程就必须去关心一下对应的子进程退出时的原因
  • 不过光凭我们现在所学习的知识还无法去查看这个,等我们后面讲到wait()系统调用的时候再去做一定的介绍

就上面这样生冷的文字来叙述还不太行,接下去我们通过实际的案例来观察一下🔍

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 
  5 int main(void)
  6 {
  7     pid_t id = fork();
  8     if(id == 0)
  9     {
 10         // child
 11         int cnt = 5;
 12         while(cnt)
 13         {
 14             printf("I am child, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
 15             sleep(1);
 16 
 17             cnt--;
 18         }                                                                                                    
 19         exit(0);
 20     }
 21     else
 22     {
 23         // father   
 24         while(1)
 25         {
 26             printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
 27             sleep(1);
 28         }
 29     }
 30     return 0;
 31 }
  • 运行起来可以看到,在一开始的 5s 内,父子进程还是同时在跑的,但是当子进程在循环结束后退出了。不过此时呢父进程还在运行并没有退出。所以我们在右侧查看这两个进程的状态时间就发生了一定的变化

在这里插入图片描述

  • 仔细地来看一下这个状态的变化,其中【PID】为5486,其父进程为5485,一开始它们均为S睡眠状态,但是呢到了后面子进程的状态就变成了Z僵死状态。也就意味着此时子进程已经死亡了,但是呢父进程还不知道
  • 这里还可以通过右侧的这个<defunct>这个来看,它表示【失效的、不再存在的】

在这里插入图片描述

所以我们总结一下:

💬 进程一般退出的时候,一般其不会立即彻底退出。如果父进程没有主动回收子进程信息,子进程会一直让自己处于Z状态,这也是为了方便后续父进程读取子进程的相关退出结果。

那对于上面的这种子进程,我们就将其称作为是【僵尸进程】,不过呢这种进程是存在一定危害的!

  • 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间
  • 那么对于上面的这种危害我们就称作为是【内存泄漏】,要如何去进行避免呢,之后的文章会做讲解~

② 孤儿进程

  • 上面我们讲到,当一个子进程突然退出但是父进程并没有去主动回收的话,那么此时这个子进程就会变成【僵尸进程】
  • 那看到下面我们将这个进程突然终止之后,父子进程都不见了

在这里插入图片描述
💬 那此时我想问:这个父进程突然之间退出了,但是呢它的父进程并不知晓,那为何这个父进程没有处于【僵尸状态Z】呢?

  • 因为当前这个进程是bash的子进程,当其一退出之后,bash就将其回收了。可是这个子进程为什么也没了呢?这个的话我们就要聊聊【孤儿进程】了

接下去我们来将上面测试僵尸进程的代码做个修改,让父进程先于子进程退出

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 
  5 int main(void)
  6 {
  7     pid_t id = fork();
  8     if(id == 0)
  9     {
 10         // child
 11         int cnt = 500;
 12         while(cnt)
 13         {
 14             printf("I am child, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
 15             sleep(1);
 16 
 17             cnt--;
 18         }
 19         exit(0);
 20     }
 21     else
 22     {
 23         int cnt = 5;
 24         // father   
 25         while(cnt--)                                                                                
 26         {
 27             printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
 28             sleep(1);
 29         }
 30     }
 31     return 0;
 32 }
  • 然后通过进程状态追踪来看看,我们观察到一开始两个父进程和子进程是同步在走的,但是呢过了一会父进程终止了,只有子进程还在跑,此时我们需要留意的不是其进程状态,而是这个子进程的PPID即父进程的PID,它由原先的【19956】变成了【1】,这是为什么呢?

在这里插入图片描述

  • 这里的话就要给读者普及一下了,对于PID的值为1的进程,我们一般将其称作为是【系统进程】。我们可以使用下面这句指令去查找一下
ps ajx | head -1 && ps ajx | grep systemd
  • 然后我们看到这个【系统进程】的PID即为1

在这里插入图片描述

那为何会出现上面这种现象呢?是这个子进程突然换父进程了吗?

  • 对于父子进程来说,如果父进程先于子进程结束的话,那么这个子进程就会被称作为是【孤儿进程】,对于孤儿进程来说呢,它会有1号进程即【系统进程】所领养,因为此时这个进程没有了父进程了,所以需要有人去带领它

💬 但是这个孤儿进程为什么要被领养呢?

  • 其实很简单,既然它是一个进程的话,最终都是要退出的,但是没了父亲的话就只能由系统进程来进行维护了

那其实我们就可以解释上面的事情了

  • 当这个父进程退出的时候,它是被bash给回收了,那么对于父进程的子进程来说,我们刚才谈到了,如果一个进程的父进程先于子进程结束的话,那么在这一刻这个子进程立马变成了【孤儿进程】,被系统领养并回收了,所以我们就看不到了

在这里插入图片描述

四、总结与提炼

最后来总结一下本文所学习的内容📖

  • 在本文中,我们主要讲解了两个大点,一个是在操作系统中所常见的一些进程状态,它们分别为:运行状态、阻塞状态、挂起状态
    • 所被调度的、处于运行队列里的这些进程所处的状态我们称之为 运行状态R
    • 处于等待队列中,同时在等待外设相应的进程所处的状态我们称之为 阻塞状态R
    • 当把一个进程的 数据和代码都重新交换到外设当中,进程所处的状态我们称之为 挂起状态R
  • 接下去我们又介绍了在Linux系统下的七种进程状态,分别是:运行状态R、浅度睡眠状态S、深度睡眠状态D、停止状态T、进程跟踪状态t、死亡状态X、僵死状态Z
    • 当一个进程没有在等待IO流的时候,其就会处于 运行状态R
    • 使用sleep()函数可以很好地使一个进程处于浅度睡眠状态S
    • 如果不想让一个进程在等待磁盘操作的时候被操作系统杀掉,则可让其处于 深度睡眠状态D
    • 可以向一个进程发送【19】号信号使其暂停,处于停止状态T继续发送【18】号信号的话则可以使其重新启动
    • 在【gdb】的环境下去运行一个断点的话则可以使其处于进程跟踪状态t
    • 使用kill -9 PID就可以杀掉一个进程,使其处于死亡状态X
    • 如果让一个子进程在父进程不知晓的时候退出,那么其就会处于僵死状态Z,变为【僵尸进程】;若是在父子进程中父进程先于子进程退出的话,那么这个子进程就会变成【孤儿进程】

以上就是本文要介绍的所有内容,感谢您的阅读🌹

在这里插入图片描述

Logo

更多推荐