【Linux】第十三站:进程状态

文章目录

  • 一、进程状态
    • 1.运行状态
    • 2.阻塞状态
    • 3.挂起状态
  • 二、具体Linux中的进程状态
    • 1.Linux中的状态
    • 2.R状态
    • 3.S状态
    • 4.D状态
    • 5.T、t状态
    • 6.X状态(dead)
    • 7.Z状态(zombie)
    • 8.僵尸进程总结
    • 9.孤儿进程总结

一、进程状态

在我们一般的操作系统学科中,它的进程状态是:运行、阻塞、挂起

image-20231107172143988

上图中的新建状态就是,一个进程刚刚创建出来的时候,即PCB刚刚创建出来

终止状态就是已经不用了,代码被运行完了

运行状态就是正在被调度的状态

1.运行状态

如下图所示,我们知道,我们的程序在运行的时候,想运行的进程是非常之多的,而CPU在极端场景下只有一个。所以这么多进程就要存在竞争。而因为调度器的存在,就让他们较为均衡的使用

image-20231107174128101

所以每一个CPU都要维护一个它自己的运行队列(struct runqueue),它可以对这些进程做出调度

struct runqueue
{
    //运行队列
    struct task_struct* head;
    struct task_struct* tail;
};

如下图所示,CPU就可以直接从运行队列中拿到一个进程进行执行。

image-20231107182529959

这就叫做进程在CPU上进行排队,该队列叫做运行队列。

而调度器就是一种函数,可以将这个运行队列作为参数传进来,从而就可以找到所有在排队的进程。

而在运行队列中的所有进程,都是R状态,即运行态。

image-20231107183024704

运行态就是我已经准备好了,随时可以被调度!!!只要处于运行队列,都是运行态

当我们创建好一个新进程的时候,只要它入队,就是运行态,只要等调度它就行了

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

不是!就比如我们前面所写的while死循环代码。

为了防止某个进程上去以后就下不来了,陷入了死循环,所以每一个进程就有一个时间片的概念,它也是PCB里面的一个数值。比如说是10ms,一旦超过这个10ms,计算机就要强制的把这个进程从CPU上扒下来,放到队列尾部。

所以在一个时间段内,所有的进程代码都会被执行!

对于上面着这种我们也叫做并发执行。

所以大量的把进程从CPU上放上去,拿下来的动作----也叫做进程切换

2.阻塞状态

我们知道操作系统的管理的核心就是先描述、在组织。

如下是我们的操作系统的结构

image-20231107185300186

操作系统要管理下面的设备、外设。就需要先描述后组织的方式

对于进程管理是隶属于软件的,而对于硬件的管理也可以用类似的方式。

struct dev
{
	int type;
	int status;
	struct task_struct* waitqueue;
    //............
};

image-20231107185722691

假设现在有一个进程,它的任务是从键盘中读取数据

image-20231107190329525

我们现在不给他输入,那么它就会等待,而且它也无法放入运行状态中,因为它当前的软硬件资源没有就绪。

所以它就会等待某种资源,而这在操作系统中只需要将他放入到键盘所对应的等待队列中

当未来还有数据需要进行输入的时候,这时候就需要往后面排队了

image-20231107190708854

同理每一个设备都可能这样的情况。

当未来我们这个键盘给这个进程数据的话,那么它就就绪了,可以进入运行队列了,从而CPU就可以去调度了

所以我们将这个正在等待排队的进程称作阻塞状态,每个这样的设备都有一个等待队列。当进程从读取到数据的时候,就会唤醒:从阻塞状态改为R状态

3.挂起状态

我们现在有一个设备叫做磁盘

image-20231107191728197

如果我们当前有多个进程正在等待键盘这个资源,可是这个键盘的资源一直在就绪,因为没有人摁它。

所以这些进程,只能以阻塞状态在等待队列中等待

可是如果,在等待的时候,操作系统的内存资源严重不足了。

所以操作系统就需要在保证正常的情况下,省出来内存资源

以阻塞状态为例,只要它没有运行的那一刻,我们当前进程的代码和数据其实是在内存里处于空闲的,没有被使用的。

所以此时操作系统会将这些进程的PCB给保留,将这个代码和数据放到磁盘外设中。

所以就相当于这个进程只有一个PCB在排队,而它自己的代码和数据在外设里面。

当这个进程就绪了,要放到运行队列了,在考虑将这个代码和数据重新放回来。

这个过程就是换出和换入的过程

image-20231107192537269

如果一个进程只有它的自己的PCB在,而代码和数据都换出了,并没有在内存当中,那么此时就是挂起状态

如果所有的进程都这样做,那么瞬间操作系统就能腾出一大块空间

其实我们现在场景下所说的挂起应该称作阻塞挂起状态

当然还有运行挂起,就绪挂起…等等,我们都不去考虑

二、具体Linux中的进程状态

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

2.R状态

Linux中R状态就是运行状态

我们先看下面的代码

image-20231108202659112

当我们运行的时候,我们可以去查看它的运行状态

image-20231108202145737

我们也可以在重新多运行一下

image-20231108203025980

我们注意到的现象是,大部分情况下都是S状态,极少部分情况是R状态

我们我们的代码明明就是在运行,却显示是S状态呢?

我们先将代码稍作修改

image-20231108203251127

然后在运行

我们就可以注意到,现在都是R状态了

image-20231108203328945

那么这是为什么呢?

这是因为我们用我们自己的感受去揣测了CPU的速度

因为我们刚刚一直在printf,需要访问外设(显示器)。我们的设备并不一定能处于一个直接写入的状态,所以我们的这个进程有非常大的概率在等待

而我我们将printf去掉以后,一直在快速的循环,就一直处于运行态了。

我们也可以用top命令,就相当于任务管理,可以随时查看进程

image-20231108204709165

同时我们也可以注意到,我们前面使用ps命令查看的时候,状态后面有个+号。这个加号代表着这个进程在前台运行。(前台运行意味着我们继续输入其他指令是没有任何反应的)

如果我们要在后台运行一个进程,我们在后面加上一个&即可

image-20231108205207410

像这种后台进程,我们只能通过kill命令来杀掉了

image-20231108205313990

3.S状态

我们将代码改为如下

image-20231108205532082

这个时候我们查到的进程大概率都是S状态

image-20231108210315836

这个就是因为CPU太快了,而这个进程要访问外设,这就显得太慢了

不过我们还可以用下面这个例子会更加直观

image-20231108211458521

image-20231108211604348

而这个状态就和我们操作系统学科中的是一样的。

也就是说linux中的S状态就是操作系统中的阻塞状态

而像我们操作系统中的一般都是阻塞状态,即在等待某种资源就绪。

image-20231108211907794

所以所谓的阻塞状态,就是在等待某种资源就绪

像我们前面的scanf就是在等键盘输入,像printf就是在等显示器就绪

4.D状态

在linux中,除了S状态是阻塞状态以外,还有一种状态也是阻塞状态,D状态,不过它也叫做深度睡眠;而S状态我们也称为浅度睡眠

两者的区别就是,S状态,即浅度睡眠是可以被唤醒的

也就是说,虽然这个进程还在继续,但是我们可以用kill去杀了他,即随时可以相应外部的变化

像下面的这种就是处于浅度睡眠的

image-20231108212741385

而深度睡眠就是不可以被唤醒的

D状态(disk sleep磁盘休眠)

比如下面这个例子

假设现在有一个进程,它想向磁盘中写入1GB数据

但是这个过程是需要画出一定的时间的。而在这段时间内,这个进程就必须得在这里等磁盘把数据全部写完,等他反映的结果。

如果此时OS的内存压力很大,它现在已经把能置换的资源全部置换了。那么操作系统如果看这个进程不爽,直接就会把他杀掉

也就说,当OS内存压力很大的时候,就会杀掉一些不重要的进程。

即如果OS可以的话,那就直接去用内存,如果扛不住了,那就尝试置换,如果还是扛不住,那就只能杀掉进程了。

这时候,如果一旦磁盘写入失败,还发现了进程已经被杀了,此时就无法回应给进程了,磁盘也无法做出决策。这时候就看具体硬件的做法了,有的硬件会直接丢掉这个数据,有的会尝试在写一次。

如果被丢失的这1GB数据很重要,那么就糟糕了


为了解决上面这个问题,所以我们需要保证让进程在等待磁盘写入完毕期间,这个进程不能被任何人杀掉,就可以了

所以说当一个进程正在等待磁盘写入数据的时候,不能设置为S状态,必须得设置为D状态,D状态就不能被任何人所杀掉

当这个数据全部写完以后,这个进程再将D状态恢复为R状态

同时也意味着,如果操作系统里面只要出现一个D状态,那么操作系统已经即将崩溃了

深度睡眠不可以被杀死的原因就是:不相应任何请求

如果我们想要模拟一下的话,可以使用dd命令,这个可以模拟高IO的情况

5.T、t状态

T状态我们称为暂停状态,也叫做stop状态

我们使用如下代码

image-20231108220000628

当我们运行的时候,是处于S状态的

image-20231108220207981

在linux中有一个信号的东西,如下我们之前杀掉进程用的是9号信号

image-20231108220253917

然后在这里我们可以给他发送18和19号信号

19号进程SIGSTOP它的作用将一个进程给暂停,如果我们将他暂停后想重新运行起来那就发送18号信号SIGCONT

image-20231109121015354

如上就是变成了T状态了,当然也可以用18号信号将他给继续跑起来

image-20231109121113454

我们可以看到,这个先暂停后再恢复,它就没有了+号了,就说明它已经变成了后台运行了

那么这个T状态和S状态有什么区别呢?

其实是有的,否则也不会分成两种状态了。

S状态一定是为了等待某种资源就绪的,而T状态暂停后当然也可以等待某种资源就绪,当然也有可能是为了等待其他事件发生后才继续执行,它是不接收除了某些信号之外的其他请求的。也就是说,T状态有可能我们只是单纯的想让他暂停一会。

不过我们暂时可以将他理解为也是某种阻塞状态

如下所示,当我们在使用gdb调试的时候,这个程序就处于t状态(T、t我们暂时不做区分)

image-20231109122506216

6.X状态(dead)

所谓的X状态就是我们前面操作系统中的终止态,就相当于一个进程结束了。

然后就可以将这个进程放到一个垃圾回收的队列中,最终操作系统就会将这些回收掉

7.Z状态(zombie)

当一个进程死亡的时候,在死亡之时不会立即进入X状态,而是会先进入Z状态(僵尸状态)

即一个进程在退出时,操作系统会先将这段信息维持一段时间。这段时间就是僵尸状态

而一个进程在退出时候,只有父进程最关心这个信息

我们可以用如下代码来进行验证。在如下代码中,子进程循环结束以后直接退出。而父进程中并没有针对子进程做出任何事情,也就意味着,一旦子进程退出了,这个父进程还在,但是啥也不干,也就意味着子进程将一直维持一种僵尸状态,它要一直等父进程来获取它的退出信息

image-20231109125816923

同时运行这段代码和下面的指令

while :; do ps ajx | head -1 && ps ajx | grep mytest;sleep 1 ; done

运行结果如下所示

image-20231109130926824

我们可以看到,当子进程退出了以后,它这个进程处于Z状态了,并且后面出现了defunct这个单词,它的意思是无效的,死者,死人

这就是僵尸状态的进程

进程一般退出的时候,如果父进程没有主动回收子进程信息,子进程会让自己一直处于Z状态,进程的相关资源尤其是task_struct结构体不能被释放

我们把处于Z状态的进程也称之为僵尸进程

内存一直会被占用着,就会发生内存泄漏了

不过我们还要注意

image-20231109131637979

当我们将程序将父进程也结束的时候,子进程也被退出了,它也没有处于僵尸状态。

这是因为,父进程退出的时候,它也有自己的父进程bash,bash瞬间将父进程给回收了,所以没有看到父进程处于僵尸状态。

前面是子进程先退出的案例,我们在看一个父进程先退出的案例

image-20231109133336929

运行结果如下

image-20231109133549456

我们发现当父进程挂掉以后,它的子进程的父进程变为了1

那么这是为什么呢?我们先看一下1号进程是什么

image-20231109135305345

可以看到就是系统的进程

image-20231109135441730

即1号进程就是操作系统本身

所以我们得到以下结论:

如果父子进程中,父进程先退出,子进程的父进程会被改为1号进程(操作系统)

父进程是一号进程,我们将这个进程称为孤儿进程

该进程被系统所领养!

那么为什么要被领养呢?

因为孤儿进程未来也会退出,也要被释放。

那么为什么不让bash领养呢?

bash做不到,bash只是创建的它的子进程,无法管理它的孙子进程。而操作系统可以直接从内核去释放掉

8.僵尸进程总结

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

9.孤儿进程总结

  • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  • 父进程先退出,子进程就称之为“孤儿进程”
  • 孤儿进程被1号init进程领养,当然要有init进程回收

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/137691.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电子学会2023年9月青少年软件编程(图形化)等级考试试卷(四级)真题,含答案解析

青少年软件编程(图形化)等级考试试卷(四级) 一、单选题(共10题,共30分) 1. 角色为一个紫色圆圈,运行程序后,舞台上的图案是?( )

C++算法:包含三个字符串的最短字符串

涉及知识点 有序集合 字符串 题目 给你三个字符串 a ,b 和 c , 你的任务是找到长度 最短 的字符串,且这三个字符串都是它的 子字符串 。 如果有多个这样的字符串,请你返回 字典序最小 的一个。 请你返回满足题目要求的字符串。…

RGMII回环:IDDR+ODDR+差分接口

目录 一、实验内容二、原理解释三、程序1、顶层文件:2、子模块2.1 oddr模块2.2、iddr顶层模块2.3、iddr子模块 3、仿真4、注意5、下载工程及仿真 一、实验内容 1、通过IDDR和ODDR的方式完成RGMII协议; 2、外部接口使用OBUFDS、IBUFDS转换成差分接口&…

2023/11/12总结

踩坑记录: org.springframework.jdbc.BadSqlGrammarException: ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column elm.flavors.id which is …

连通块中点的数量(并查集)

给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。 现在要进行 m 个操作,操作共有三种: C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;Q1 a b,询问点…

TensorFlow学习笔记--(3)张量的常用运算函数

损失函数及求偏导 通过 tf.GradientTape 函数来指定损失函数的变量以及表达式 最后通过 gradient(%损失函数%,%偏导对象%) 来获取求偏导的结果 独热编码 给出一组特征值 来对图像进行分类 可以用独热编码 0的概率是第0种 1的概率是第1种 0的概率是第二种 tf.one_hot(%某标签…

木疙瘩踩坑日记-容易忽略的一些BUG

在一开始玩家务必很清楚这三个概念 图形:舞台上元素的最小单位。软件自带的以及外部导入的图片默认都是图形!最朴素的元素!可以添加预制动画、关键帧动画、进度动画(软件自带的形状) 元件:一个可以内部封…

阿里云国际站:全球加速GA

文章目录 一、前言 二、阿里云全球加速的概念 三、阿里云全球加速的功能优势 四、阿里云全球加速的原理 五、阿里云全球加速的应用场景 六、写在最后 一、前言 随着互联网的快速发展,网站速度已经成为了用户访问体验的一个重要指标。阿里云加速作为一种新的技…

Web开发:一键复制到剪切板功能实现思路

在很多网页页面中我们都使用到过一键复制内容到剪切板的小功能,那么,具体如何实现呢?下面来讲述基于原生JavaScript API的两种实现思路。 同步方式:document.execCommand 这种方式: ①优点:是最传统的方法…

把字符串转换为整数函数atoi

今天我们来认识一个函数&#xff0c;叫atoi&#xff0c;我们开始研究它吧&#xff01; 1.认识atoi 1.函数功能&#xff1a;将字符串转换为整数 只能将整数字符串转换为整数&#xff0c;不能转换字符字符串 2.头文件&#xff1a;#include<stdlib.h> 3.使用格式&#xff1a…

文件上传 [ACTF2020 新生赛]Upload1

打开题目&#xff0c;发现是一道文件上传题目 随便上传个一句话木马上去 发现网站前端有白名单限制&#xff0c;只能上传含有jpg&#xff0c;png&#xff0c;gif的后缀文件 最开始我想到的做法是先上传htaccess文件&#xff0c;bp修改文件头&#xff0c;上传成功后然后再上传以…

数据结构与算法(二)动态规划(Java)

目录 一、简介1.1 什么是动态规划&#xff1f;1.2 动态规划的两种形式1&#xff09;自顶向下的备忘录法&#xff08;记忆化搜索法&#xff09;2&#xff09;自底向上的动态规划3&#xff09;两种方法对比 1.3 动态规划的 3 大步骤 二、小试牛刀&#xff1a;钢条切割2.1 题目描述…

Linux系统上64位ATT风格汇编语言计算乘方堆栈图分析(只有一层调用)

参考博文&#xff1a;《怎样深入理解堆和栈》 《关于寻址方式一篇就够了》 《堆栈、栈帧、函数调用过程》 《gdb 调试中-i frame命令之堆栈信息说明》 《【TARS】GDB 调试进阶「0x02」》 栈与栈帧的关系 一个程序在运行过程中&#xff0c;操作系统会在内存中分配多个区域给这…

设计模式-工厂方法

工厂方法是一种创建型设计模式&#xff0c;其在父类中提供一个创建对象的方法&#xff0c;允许子类决定实例化对象的类型。 问题 假设你开设了一个汽车工厂。创业初期工厂只能生产宝马这一款车&#xff0c;因此大部分代码都位于名为宝马的类中。 工厂效益非常好&#xff0c;为…

牛客刷题记录11.12 (10/6)

操作复杂度 map vector set deque 抽线类 C11 :两个新特性 &#xff1a; override, finnal override:子类必须覆写父类的虚函数&#xff0c;否则报错&#xff0c; finnal:类中函数使用后&#xff0c;子类不能重写该函数&#xff1b;若修饰类&#xff0c;该类不能被继承&#…

生成只需要4step,像lora一样使用LCM

SDXL in 4 steps with Latent Consistency LoRAs 在comfyui里实测LCM lora 原先需要20步一张图&#xff0c;现在20步&#xff0c;4张图。comfyui最新版新增了lcm采样器&#xff0c;支持lcm lora的工作流。 LCM lora模型下载&#xff1a; huggingface.co/latent-consistency/lcm…

BGP属性实验

一、实验拓扑 二、实验要求 按照图示配置IP地址以及在路由器上配置BGP&#xff0c;使其全网通 1、配置IP地址 2、配置AS 200内的OSPF [AR2]ospf 1 router-id 2.2.2.2 [AR2-ospf-1]a 0 [AR2-ospf-1-area-0.0.0.0]network 2.2.2.2 0.0.0.0 [AR2-ospf-1-area-0.0.0.0]network 1…

深入了解SpringMvc接收数据

目录 一、访问路径&#xff08;RequestMapping&#xff09; 1.1 访问路径注解作用域 1.2 路径精准&#xff08;模糊&#xff09;匹配 1.3 访问路径限制请求方式 1.4 进阶访问路径请求注解 1.5 与WebServlet的区别 二、接收请求数据 2.1 请求param参数 2.2 请求路径参数 2.3 请求…

【GEE】10、使用 Google 地球引擎创建图形用户界面【GUI开发】

1简介 在本模块中&#xff0c;我们将讨论以下概念&#xff1a; 用于生成图形用户界面的 GEE 对象。如何开发具有交互元素的面板。如何将地理处理元素连接到交互式元素。 2背景 在过去的十个单元中&#xff0c;我们展示了 Google Earth Engine 可以成为一种重要且高效的资源&a…

代码分析之-广东省公共资源交易平台

广东省公共资源交易平台 hex: function Xq() {return bg || (bg 1,function(e, t) {(function(n, u) {e.exports u()})(an, function() {var n n || function(u, o) {var r;if (typeof window < "u" && window.crypto && (r window.crypto)…