当前位置: 首页 > news >正文

Linux中进程的属性:状态

一、通用OS进程中的各种状态与相关概念

1.1通用进程中的状态

CPU执行进程代码,不是把进程执行完才开始执行下一个,而是给每个进程预分配一个“时间片”,  CPU基于时间片进行轮转调度(每个CPU分别进行)

其中发涉及到的几个进程的状态在不同操作系统中各不相同,但可以把他们归类为图中几种:

创建/就绪/运行状态(对于一个进程来说往往没有明显区别)

阻塞状态

中止状态

1.2并行与并发的概念

CPU的功能十分强大,即使有多个进程,也可以在CPU数量仅有几个的情况下,通过并行和并发来执行

①并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内让多个进程同时推进

②并行:多个进程在多个CPU下分别,同时进行

1.3时间片的概念与分时操作系统,实时操作系统

时间片是应用于分时操作系统当中的,分时操作系统的特点是:

①给每一个任务分别分配一个时间片

②调度任务尽量公平的分配(即时间片尽量贴近)

类似于Linux和Windows这种民用级别操作系统,都采用分时操作系统

与之相对的是实时操作系统

如汽车上的操作系统,在播放音乐和紧急刹车这两个任务间,紧急刹车必须要是最高优先级,不能再尽量公平了

1.4运行状态

1.4.1分时操作系统的大致原理

首先操作系统在启动电脑时就会加载到内存中

每个CPU都会对应一个runqueue队列的对象,对象中存有PCB的头节点(这里以Linux中的task_struct为例),在不考虑优先级问题时,遵循FIFO算法

1.4.2FIFO算法的具体实现

当一个新的进程进入的时候,会形成一个task_struct,之后链入到runqueue对象的task_struct* head中

CPU在调用时,每次把head直接指向的task_struct对象拿出来,让head指向它的next,它本身进入CPU中进行运行,运行时间片结束后链入到最后一个task_struct的下一个

1.4.3什么时候进程是运行状态

通过FIFO算法说明:当对应进程在运行队列当中,该进程的状态就叫运行状态

可以理解为已经准备好了,可以随时被CPU调度

1.5阻塞状态

1.5.1操作系统对于底层硬件的管理:device链表

OS要管理底层硬件,依旧是遵循“先描述,后组织”的原则,在OS内部创建一个名为device的结构体对象,用来表示每个硬件的情况

在内存中,各个device像带头链表一样连接起来,其属性中的 type标识了是何种硬件,status则表示当前硬件是否正常工作,如此一来,再间隔一层驱动层就可以对硬件进行直接管理

1.5.2什么时候进程处于阻塞状态

当一个进程task_struct的代码中出现了scanf()等需要从键盘或其他硬件读取数据的语句,CPU会停止运行,并且不会把它链入runqueue队列中,取而代之的是链入device的一个成员变量task_struct* wait_queue下(根据1.5.1中图示device的第四个成员变量)

如果有多个需要读取的进程,会按照队列继续向下链入

这些在硬件的wait_queue队列中等待的进程,称之为阻塞状态

1.5.3阻塞状态回归运行状态的过程

在OS接收到如键盘等硬件输入的信息后就会把这一进程的阻塞状态改为运行状态,并且将其从waitqueue头删,链入到runqueue队列中

在该进程再次进入CPU中时,就已经获取了键盘的写入数据,并继续接下来的程序

运行和阻塞的本质:让不同进程处在不同的队列中

1.5.4阻塞(又名等待)的本质是什么

连入目标外部设备,CPU不调度

1.6挂起状态

1.6.1出现的契机

处于阻塞状态的进程不会被CPU调度,但是他的代码和数据还留在内存中占用空间;

此时如果内存空间严重不足,那就可以考虑暂时将这一部分代码和数据移到硬盘中管辖需要使用时再调回来

1.6.2意义

在内存资源严重不足的时候,给操作系统一些空间,本质上是用时间换空间(因为换入换出的本质是IO,速度很慢,实际应用时不多见)

1.6.3阻塞挂起状态

所谓挂起状态,就是在磁盘中一块称为“swap分区”的位置,把内存中的代码和数据换出到磁盘的相应位置,换出后,因为一定是在阻塞状态下才有机会进入挂起状态,此时进程的状态称为“阻塞挂起状态

回归运行:

当硬件中获取到数据,OS会先把状态改为运行再从内存中换出代码和数据,然后再连入运行队列

二、Linux中进程的状态

2.1底层中状态定义的方式与各类状态

在Linux中,是用task_struct中的属性int status

通过一个类似于#define的方式整形定义为为各类状态

R(running):0

S(sleeping):1

D(disk sleep):2

T(stopped):4

t(tracing stop):8

X(dead):16

Z(zombie):32

①R

就是普通的运行状态

②S

是sleeping的缩写,为阻塞等待状态(可中断睡眠;又称浅睡眠,可以被kill杀进程)

③D

是disk sleeping磁盘睡眠的缩写,为阻塞等待状态的一种(不可中断睡眠;又称深睡眠)

④T

是stopping的缩写,也属于阻塞

⑤t

是tracing stop追踪暂停的缩写,当进程被追踪的时候(如gdb中断点停下,进程状态为t)也属于阻塞

⑥X

是dead的缩写,死亡状态

⑦Z

是zombie的缩写,僵尸状态

2.2D状态

disk sleeping,即磁盘睡眠

因为磁盘负责存取数据,这很重要,所以操作系统中的进程在磁盘当中等待的状态比较特殊,它的出现是因为要向磁盘当中写入数据是做IO操作,需要花很多时间

而且这段时间进程不能出任何问题,否则会造成数据的丢失,所以单独设置了一个深睡眠防止被杀进程

注:D状态大多的情况下是瞬时的状态,不会被查到;因为如果查到,那么大概率是磁盘出现了一些问题,如老化,空间不足等。

2.3S状态与S+状态的区别

他们的区别主要在是否是后台进程上:一般一个可执行程序启动后默认都是前台程序,状态对应S+;后台进程则对应状态S

实例:

假如运行起来一个可执行程序mycode

我们可以通过

./+[可执行程序] &

启动后台进程

效果

查询结果:

后台进程的特点是什么?

无法被ctrl+C杀进程,只能通过kill -9来结束程序

前台进程:

后台进程:

 

用kill -9杀进程后:

2.4T状态的主动设置与取消以及其影响

T状态的设置有两种方式:

①当进程做了非法但是不致命的操作,被OS暂停

②可以利用kill的选项指定一个进程进入T状态

第19号选项SIGSTOP,可以使用第18号选项SIGCONT将进程继续

用法:

kill -19 +[PID]

2.4补:主动设置T状态后取消会影响到进程的前后台属性

主动设置T状态后再继续,会自动将前台程序切换到后台

2.5X状态与进程死亡

2.5.1进程退出时的返回信息

要了解进程退出时的返回顺序,那么必须要明确:进程为什么会被创建

进程被创建是为了完成用户的任务

既如此,在进程退出时,我们必须要明确任务是否被完成,那么该怎么做呢?

实际上,我们通过进程的执行结果,告知父进程/OS任务的完成情况

那么我们该如何查看进程的执行结果呢?

可以通过指令

echo $?

查看最近程序退出时的退出信息(例如ll之后运行,就会展示ll指令是否成功)

 注:程序正常完成任务返回0,其余情况返回非0

2.5.1补:“程序正常完成任务返回0,其余情况返回非0”这种说法的实例

我们在写main函数的时候,会在最后一行写上return 0; 其实这一个返回值就是返回给OS进程的执行结果,返回0就是程序正常执行完毕

如果我们刻意main函数写返回10,$?结果也会变为10

2.6死亡状态X与僵尸状态Z

2.6.1通过现实例子理解二者对于进程结束的影响

正如在苹果从树上掉下腐烂后,我们研究它落地时间最后扔掉它的过程

苹果在树上成熟,它落地后腐烂,直到我们发现并对它进行检测研究结束,它的腐烂时间这一数据就被记录了下来,最后他才会被扔掉

这个过程类比就是:

在树上生长就相当于进程在运行

从树上落下相当于运行结束

结束后被我们发现并研究出腐烂时间,类比于“维持退出信息,方便父进程/操作系统来进行查询”

从掉下到研究完毕,即从进程结束到维持完退出信息,称之为僵尸状态

扔掉后,即进程死亡状态

2.6.2进程退出时发生的事情

进程=内核数据结构(task_struct)+代码和数据

①进程退出时,代码就不会再被执行了,首先可以立即释放的就是程序代码和数据

②进程退出时需要有退出信息,这些信息就保存在自己的task_struct中

③虽然退出,但它的task_struct依旧要被OS维护起来,方便用户未来获取进程退出信息

2.6.3退出信息的存储位置:task_struct中

task_struct中存储内容之一就是“进程退出信息”,对应的形式是int类型,名为exit_code

以及一些其他的信息

2.6.4进程创建/释放时,内核数据结构和代码数据出现的先后顺序

进程创建:先创建内核数据结构task_struct(没加载代码数据之前称为新建状态),再创建代码和数据

进程释放:先释放代码和数据,此时称之为僵尸状态,task_struct暂时被维护起来

2.6.5结合例子观察僵尸状态(死亡状态为瞬时状态,无法观察)及系统层面的内存泄漏

首先需要一个代码:可以展示父子进程同时进行,且子进程进行十次以后停止,但父进程一直运行 

 4 int main()5 {6     printf("父进程开始执行;我的PID:%d,我的PPID:%d\n",getpid(),getppid());7 8     pid_t pi=fork();9     if(pi==0)10     {11         int cnt=10;12         while(cnt > 0)13         {14         printf("我是子进程,我的PID:%d,我的PPID:%d,当前cnt:%d\n",getpid(),getppid(),cnt);15         sleep(1);16         cnt--;17         } 18     }19     else {20         while(1)21         {22         printf("我是父进程,我的PID:%d,我的PPID:%d\n",getpid(),getppid());                                                                             23         sleep(1);24         25         }26     }27 28 29     return 0;30 }

我们编辑出一个名为“status”的可执行程序

为了可以实时监控进程的状态,我们需要使用“循环执行指令”的方式(暂不展开讲述循环方法)

while :;do ps axj |head -1;ps axj | grep status;sleep 1;done

执行结果:

在刚开始执行./status程序时,进程的状态一直是“S+” 

在子进程循环十次以后,状态发生了改变,成为了“Z+”

从这个结果我们可以看出:子进程执行完了以后,程序进入僵尸状态;这个状态是为了维护自己的task_struct,方便未来父进程读取退出状态(父进程不读,子进程PCB不退),对于此时的子进程来说,OS和父进程都不会直接回收他

其中“defunct”意为“失效的”,即表示进程已经开始退出

系统层面的内存泄漏:

在例子中,如果没有人管子进程,他会一直处于僵尸状态,而task_struct是一个不小的对象,他一直在消耗内存,这就是内存泄漏(系统层面)

解决:

一般需要父进程主动读取子进程退出信息,子进程就会自动退出 

2.6补:我们malloc/new出来的空间会随着进程结束而自动释放吗(语言层面内存泄漏)

会的,malloc/new出来的空间属于“代码和数据”中的“数据”这部分,进程退出,代码和数据自动释放

所以内存泄漏(语言层面)主要怕一直不退的常驻进程,这样new出来的空间一直不还

2.7孤儿进程

2.7.1对比2.6.5中的例子,理解孤儿进程的出现

父在子退会出现僵尸进程,而父退子在就会出现孤儿进程

修改例子中的代码:

  4 int main()5 {6     printf("父进程开始执行;我的PID:%d,我的PPID:%d\n",getpid(),getppid());7 8     pid_t pi=fork();9     if(pi==0)10     {11         int cnt=10;12         while(cnt > 0)13         {14         printf("我是子进程,我的PID:%d,我的PPID:%d,当前cnt:%d\n",getpid(),getppid(),cnt);15         sleep(1);16 17         }18     }19     else {20         int cnt=10;21         while(cnt > 0)22         {                                                                                                                                                23         printf("我是父进程,我的PID:%d,我的PPID:%d,当前cnt:%d\n",getpid(),getppid(),cnt);24         sleep(1);25         cnt--;26         }27     }28 29 30     return 0;31     }

我们让父进程只执行10次,而子进程无限循环

效果: 

 我么不难发现:子进程的PPID变为了1,那么这里的1究竟是什么呢?

2.7.2利用top可以快速查看当前所以进程,寻找PID为1的是谁

从中可以读出:PID为1对应的是“systemd”,即内存中加载的操作系统

父进程退出后,子进程会被“系统”(部分OS下名为initd)自动领养

2.7.3孤儿进程的影响:转到后台

被“领养”的子进程会自动装到后台去运行,它不能被ctrl+c杀进程,需要kill -9来进行中止

相关文章:

  • 18487.1-2015-解读笔记五-交流充电之停止充电
  • AI与Web3.0:技术融合
  • C#语言实现PDF转Excel
  • 26、C# 中是否可以继承String类?为什么?
  • GD32F407单片机开发入门(六)定时器TIMER详解及实战含源码
  • Redis 服务自动开启、设置密码和闪退问题
  • .NET、java、python语言连接SAP系统的方法
  • 深入探究Python中`__init__.py`文件的奥秘
  • 【AI应用】免费代码仓构建定制版本的ComfyUI应用镜像
  • 声纹振动传感器在电力监测领域的应用
  • 数据一致性问题剖析与实践(三)——分布式事务的一致性问题
  • Spring Boot中的监视器:Actuator的原理、功能与应用
  • JavaScript 防抖和节流
  • JavaFX 第一篇 Hello World
  • 在线测试来料公差
  • 【开源】STM32HAL库移植Arduino OneWire库驱动DS18B20和MAX31850
  • 香港科技大学广州|先进材料学域博士招生宣讲会—南开大学专场
  • OpenCV 图形API(54)颜色空间转换-----将图像从 RGB 色彩空间转换到 HSV色彩空间RGB2HSV()
  • day001
  • 计算机网络笔记(七)——1.7计算机网络体系结构
  • 建投读书会·东西汇流|东西方戏剧在上海的相逢、交锋与融合
  • 【社论】高度警惕AI谣言对网络空间的污染
  • 几百元的工资优势已不能吸引人才流动,江苏多地探讨“抢人”高招
  • 民生访谈|电动自行车换新补贴会优化吗?今年汛期情况如何?市应急局回应
  • 安徽一季度GDP为12265亿元,同比增长6.2%
  • 国家新闻出版署:4月共118款国产网络游戏获批