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

news2024/11/18 14:39:25

文章目录

  • 一、进程状态
    • 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进程回收

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于springboot实现家具商城管理系统项目【项目源码】

基于springboot实现家具商城系统演示 Java语言简介 Java是由SUN公司推出,该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称,也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备了显著的优势…

Linux学习第41天:Linux SPI 驱动实验(二):乾坤大挪移

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 本章的思维导图如下: 二、I.MX6U SPI主机驱动分析 主机驱动一般都是由SOC厂商写好的。不作为重点需要掌握的内容。 三、SPI设备驱动编写流程 1、SP…

Unity中Shader的雾效

文章目录 前言一、Unity中的雾效在哪开启二、Unity中不同种类雾的区别1、线性雾2、指数雾1(推荐用这个,兼具效果和性能)3、指数雾2(效果更真实,性能消耗多) 三、在我们自己的Shader中实现判断,是…

Java常用类和基础API

我是南城余!阿里云开发者平台专家博士证书获得者! 欢迎关注我的博客!一同成长! 一名从事运维开发的worker,记录分享学习。 专注于AI,运维开发,windows Linux 系统领域的分享! 本…

Spark SQL 每年的1月1日算当年的第一个自然周, 给出日期,计算是本年的第几周

一、问题 按每年的1月1日算当年的第一个自然周 (遇到跨年也不管,如果1月1日是周三,那么到1月5号(周日)算是本年的第一个自然周, 如果按周一是一周的第一天) 计算是本年的第几周,那么 spark sql 如何写 ? 二、分析 …

蓝桥杯 插入排序

插入排序的思想 插入排序是一种简单直观的排序算法,其基本思想是将待排序的元素逐个插入到已排序序列 的合适位置中,使得已排序序列逐渐扩大,从而逐步构建有序序列,最终得到完全有序的序 列。 它类似于我们打扑克牌时的排序方式&…

缅因州政府通知130万人MOVEit数据泄露事件

大家好,今天我要向大家通报一个令人震惊的消息:缅因州政府的系统遭到了入侵,黑客利用MOVEit文件传输工具的漏洞,获取了约130万人的个人信息,这几乎相当于该州的整个人口数量。 MOVEit攻击是Clop勒索软件团伙进行的一次…

数据结构(超详细讲解!!)第二十三节 树型结构

1.定义 树型结构是一类重要的非线性数据结构,是以分支关系定义的层次结构。是一种一对多的逻辑关系。 树型结构是结点之间有分支,并且具有层次关系的结构,它非常类似于自然界中的树。树结构在客观世界中是大量存在的,例如家谱、…

nginx代理docker容器服务

场景描述 避免暴力服务端口,使用nginx代理 一个前端,一个后端,docker方式部署到服务器,使用docker创建的nginx代理端口请求到前端端口 过程 1 docker 安装nginx 1.1 安装一个指定版本的nginx docker pull nginx#启动一个ngi…

【MySQL】对表结构进行增删查改的操作

表的操作 前言正式开始建表查看表show tables;desc xxx;show create table xxx; 修改表修改表名 rename to对表结构进行修改新增一个列 add 对指定列的属性做修改 modify修改列名 change 删除某列 drop 删除表 drop 前言 前一篇讲了库相关的操作,如果你不太懂&…

麒麟信安:助力医疗行业操作系统自主创新,提升可靠性与安全性

应用场景 湖南省康复医院是省卫生健康委直属公立三级康复医院,也是全省唯一一所集预防、医疗、康复、科研、教学、健康管理为一体的省级三级公立康复医院。 湖南省康复医院使用的医慧管平台由湖南蓝途方鼎科技有限公司开发,利用互联网技术,…

好题分享(2023.11.5——2023.11.11)

目录 前情回顾: 前言: 题目一:补充《移除链表元素》 题目二:《反转链表》 解法一:三指针法 解法二:头插法 题目三: 《相交链表》 题目四:《合并两个有序数列》 题目五&…

vmware 修改主机名称 hadoop 服务器环境配置(一)

如何在虚拟机配置主机名称: 1. 如图所示在/etc 文件夹下有个hosts文件。追加映射关系: #关系 ip地址 名称 192.168.164.20 hadoop20 2. 保存后,重启reboot即可

炸裂!Sklearn 的 10 个宝藏级使用方法!

大家好,本次给大家介绍10个Sklearn方法,比较小众但非常好用。 1️.FunctionTransformer 虽然Sklearn中有很多内置的预处理操作可以放进pipeline管道,但很多时候并不能满足我们的需求。 如果是比较简单并且通过一个函数可以实现需求的情况&…

考前须知-2024年上半年系统集成项目管理工程师

可以看看23年下半年软考集成考试的难度 一、考试时间安排: 集成考试一年会考2次,上半年一次、下半年一次。考试时间4h,分批进行 系统集成项目管理工程师教材共655页,分为23章。其中,前3章为信息化与系统集成基础知识的内容,第4章…

4.5 构建onnx结构模型-Reshape

前言 构建onnx方式通常有两种: 1、通过代码转换成onnx结构,比如pytorch —> onnx 2、通过onnx 自定义结点,图,生成onnx结构 本文主要是简单学习和使用两种不同onnx结构, 下面以pow 结点进行分析 方式 方法一&am…

C语言之初阶指针

一、指针: 其实按照我的理解,当我们写c语言程序的时候,创建的变量,数组等都要在内存上开辟空间。而每一个内存都有一个唯一的编号,这个编号也被称为地址编号,就相当于,编号地址指针。 二、指针…

STM32F4 GPIO端口二极管作用——二极管钳位作用

如上图所示,有两个保护二极管,用于保护内部电路,防止I\O引脚外部过高或者过低的电压输入时造成内部电路损坏。 具体来讲:当引脚输入电压高于VDD时,上面的二极管导通,输入点电压被钳位到约VDD0.7V&#xff…

python打包部署脚本

linux可使用expect来实现自动交互,windows想要写出同样的功能脚本,只能使用python或者安装ActiveTcl 1、安装python Microsoft Store搜索python直接安装,默认会直接添加到环境变量https://www.python.org/官网下载,点击安装时会提…

比尔·盖茨谈他对软件应用和人工智能代理未来的见解

比尔盖茨在他的 Gatesnotes 网站中发表了一篇文章,分享了他对软件应用和人工智能代理未来的见解。他认为人工智能代理将大行其道,在接下来的五年里,这将完全改变。你不需要为不同的任务使用不同的应用程序。你只需用日常语言告诉你的设备你想…