一探Linux下的七大进程状态-2

简介: 一探Linux下的七大进程状态

3、深度睡眠状态D

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

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

image.png

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

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

image.png

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

image.png

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

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

一场有趣的官司

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

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

image.png

以下呢,是三个人的辩词

💬 【操作系统】:

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

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

💬 【磁盘】:

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

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

💬 【进程】:

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

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

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

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

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


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

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

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

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

4、停止状态T

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

  • 首先我们要通过下面这句命令来查看一下对应的进程信号


kill -l
  • 此处我们要使用到的是18、19号信号

image.png

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

  • 所以我们来总结一下
  • 暂停进程


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,原因就在于这个进程在调试的时候停了下来

image.png

6、死亡状态X

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

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


kill -9 PID

image.png

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


killall 进程名

image.png

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

image.png

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

image.png

① 僵尸进程

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

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

image.png

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



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

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

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

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

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


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僵死状态。也就意味着此时子进程已经死亡了,但是呢父进程还不知道
  • 这里还可以通过右侧的这个这个来看,它表示【失效的、不再存在的】

image.png

所以我们总结一下:

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

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

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

② 孤儿进程

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

image.png💬 那此时我想问:这个父进程突然之间退出了,但是呢它的父进程并不知晓,那为何这个父进程没有处于【僵尸状态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

image.png

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

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

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

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

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

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

image.png

四、总结与提炼

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

  • 在本文中,我们主要讲解了两个大点,一个是在操作系统中所常见的一些进程状态,它们分别为:运行状态、阻塞状态、挂起状态
  • 所被调度的、处于运行队列里的这些进程所处的状态我们称之为 运行状态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,变为【僵尸进程】;若是在父子进程中父进程先于子进程退出的话,那么这个子进程就会变成【孤儿进程】

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

相关文章
|
7月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
5月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
238 67
|
4月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
119 16
|
4月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
93 20
|
9月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
261 1
|
3月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
75 0
|
3月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
104 0
|
3月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
66 0
|
3月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
70 0
|
6月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
223 4