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

【Linux】进程状态

📝前言:

前两篇文章Linux进程概念(一),Linux进程概念(二)我们初步了解了Linux进程概念,这篇文章我们来讲讲Linux进程状态

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏


目录

  • 一,操作系统的进程状态
  • 二,Linux进程状态
    • (1)R 运行状态
      • 1.1 运行队列
    • (2)阻塞状态
      • 设备等待队列
      • PCB中如何存储指针
      • 阻塞状态
      • S 状态和 D 状态
      • 挂起状态
    • (3)X 死亡状态
    • (4)T 状态和 t 状态
    • (5)Z 僵尸状态
      • 内存泄漏
      • 内核结构申请
    • (6)孤儿状态
      • 终端,前台与后台

一,操作系统的进程状态

在操作系统这门学科(书本)里面,主要强调:进程在就绪、运行和阻塞三种状态之间转换:
在这里插入图片描述
下面先笼统的讲一下,这三中状态(在Linux的进程状态中再结合Linux系统具体讲Linux的进程状态):

  • ready就绪状态:进程创建后,系统会为其分配必要的资源,并将其放入就绪队列,此时进程进入就绪状态。
  • running运行状态:进程在运行或者在运行队列中
  • blocked阻塞状态:进程因某一时间暂停执行,如:等待 I/O 事件(等待键盘输入,等待写入操作完成…)会从运行状态进入阻塞状态

二,Linux进程状态

操作系统学科中的进程状态是一个较为抽象和通用的概念,而 Linux 进程状态则是基于操作系统原理在 Linux 系统中的具体实现

Linux的进程状态是用一个变量来表示的,这个变量是一个在task_struct里面的一个整型变量
下面是状态在kernel源代码里的定义:

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 */
};
  • R运行状态(running): 进程在运行中或在运行队列里
  • S睡眠状态(sleeping):意味着进程在等待事件完成,可中断睡眠
  • D磁盘休眠状态(Disk sleep):也是进程在等待事情完成,不可中断睡眠
  • T停止状态(stopped):主要是用户/操作系统暂停的,用户可以通过发送SIGSTOP 信号暂停进程,也可以通过发送SIGCONT信号,让进程继续运行
  • t停止状态((tracing stop)):debug的时候,在断点处停止时,就是t状态
  • X死亡状态(dead):程序运行完,且资源回收了(这个状态只是⼀个返回状态,你不会在任务列表⾥看到这个状态)
  • Z僵尸状态(zombie):当程序运行完,但是资源还未回收时,处于的状态

(1)R 运行状态

当进程在运行中或在运行队列里,就是R状态
在运行中就是进程被加载到CPU了,那什么是运行队列呢?

1.1 运行队列

运行队列(调度队列):是Linux操作系统用来管理后面几个要运行的进程的PCB的数据结构。CPU 通过从运行队列中获取进程的相关信息来决定下一步要执行的进程。


(2)阻塞状态

设备等待队列

首先,我们要知道,Linux里面的设备也是被描述了,然后把特性提取出来,用结构体储存,再用数据结构组织起来的。

并且,这些设备结构体中会有一个成员叫做:设备等待队列。当进程需要访问某个设备,但设备暂时不可用或处于忙碌状态时,进程(PBC)会被放入该设备的等待队列中。

看到这里你会不会思考,为什么PBC即可以在运行队列又可以在设备的等待队列中呢?
这就要谈谈PCB中的指针了。

PCB中如何存储指针

我们以前学的队列/链表中的节点:
在这里插入图片描述
结构体内直接存储指针变量,那当然一个结构体里面如果只有这一组pre和next,那就只能存放在一个数据结构体了。
除非你这样(再存一个指针变量,需要放在多少个不同的数据结构里,你就搞多少个不同的指针):
在这里插入图片描述
但是这样有点挫,你不同数据结构的next和prev还得区分。为此,Linux内把prevnext先封装到了一个结构体list_head里面。
这个list_head就只存储nextprev指针,然后PCB里面存储的就是list_head这一个成员。
这时候,不同的list_head就相当于不同数据结构中的两个指针,实现了PBC存在不同数据结构里:
在这里插入图片描述

但是list_head不是一个指向结构体的指针,没有结构体指针,那我们怎么访问结构体成员呢?
别怕,我们已知,list_head成员的地址,自然可以获取到这个成员和结构体“开始地址”的偏移量。
&((struct struct_task* )0->list_head):这样就可以得到list_head距离该结构体开始地址的偏移量。
C语言标准库 <stddef.h> 中定义的 offsetof 宏就可以帮我们解决这个问题。

阻塞状态

我们通过一个程序执行的过程,感受什么时候出现阻塞状态

  • 我们写了一个程序,程序里有一句scanf
  • 当程序运行到scanf的时候,就需要从键盘读取数据,进程就会等待用户输入
  • 如果用户没有按下键盘,键盘就一直处于未就绪状态,原来在运行的进程(PCB)就会从运行队列中被移动到键盘设备的等待队列,这个时候,进程就从运行状态,变成了阻塞状态
  • 直到用户输入完了,键盘就绪了,这个时候操作系统会立刻知道硬件的变化,然后操作系统就会查看就绪设备的节点,将PCB的进程状态更改(从阻塞状态改回运行状态),把PCB从设备等待队列移动回调度队列

S 状态和 D 状态

S 状态和 D 状态都是阻塞状态,它们的区别是:S 可以被中断,D不可以
怎么理解这个意义呢?讲个例子

假如现在有一个进程中有个操作要往磁盘里面写数据,那么磁盘写数据有成功也有失败,这个进程就需要等到磁盘写入完成后,才能继续下一步操作,在这个等待的过程中就是阻塞状态(假如就是 S 状态,可以被中断)

但是,假如现在的操作系统内存严重不足,操作系统就可能会把这个处于S 状态的进程直接杀掉来腾空间。直接杀掉以后,如果磁盘写入失败了,这时候磁盘回头看,已经找不到原来的进程了,那么要写入的这份数据,就会被磁盘丢弃,但是这个行为用户是不知道的,就造成了数据丢失。

为了解决这个问题,就出现了 D 状态。如,当执行写入这种高I/O的操作的时候,进程就会被设置成 D 状态,无法被杀掉。

状态演示:
S 状态:

myproc.c文件:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{while(1){printf("我是一个进程\n");sleep(1);}return 0;
}

运行并查看进程信息:

while true; do ps -axj | head -1; ps -axj | grep myproc; sleep 1; done

结果:
在这里插入图片描述
这里一直在输出printf所以是S。那为什么程序运行不显示R状态呢?
因为在这里printf的运行时间占程序的很大比重(比如99%),我们在这里每隔一秒钟打印,刚好遇到R的状态概率比较小,大部分时候都在IO

D 状态不容易观察,因为磁盘的写入速度其实也很快,如果出现秒级以上的D状态的显示出现,那可能是机器要挂了,或者磁盘出问题了。这里就不做演示了

挂起状态

这个状态Linux中没有具体的状态对应,因为挂起的操作是操作系统自己完成的,我们不用关心。
挂起分为阻塞挂起和运行挂起:

  • 阻塞挂起就是:当操作系统内存不够了,OS把不会被调度的进程先交换到磁盘上(进程是数据和代码挂到磁盘上),腾出空间。后续换回操作就是:OS通过指针找到对应的数据和代码再换回来
  • 运行挂起:内存实在不够了,把运行队列末端的进程也挂出去

(3)X 死亡状态

死亡状态就是指程序运行完毕退出了,且资源也被回收完了。X状态也无法被显示观察到。


(4)T 状态和 t 状态

T 状态和 t 状态都是暂停状态。

  • t 状态:在程序debug的时候,在断点位置处的暂停就会进入t状态

状态演示(在第8行打一个断点,然后运行):
在这里插入图片描述
在这里插入图片描述

  • T 状态:主要是通过用户发信号的暂停,或者是操作系统的暂停。用户发信号的暂停就是通过:kill -信号编号 进程PID。系统的暂停的就是当操作系统怀疑程序有问题时,暂停程序(而不是直接结束),然后给用户,让用户自己排查问题,相当于一种“止损”操作。-19是暂停,-20是重新启动

状态演示:
我们让进程运行起来,然后手动暂停
在这里插入图片描述
状态变化:
在这里插入图片描述


(5)Z 僵尸状态

Z僵尸状态就是:当进程已经结束,但是还未回收资源(PCB)

子进程在运行结束以后,代码和数据都会被释放,但是进程的运行结果信息会被保存在task_struct留给父进程(父进程需要知道子进程运行的怎么样)。【也就是说PCB是不会直接在结束的时候释放的,这时候就进入了僵尸状态】
父进程可以在这个状态内获取子进程的退出信息,并且需要回收子进程的PCB(如何回收先不讨论)
如果没有回收,那么子进程的PCB就一直不会被释放,就会造成内存泄漏

示例(我们让子进程运行完,然后父进程一直运行,使得父进程没办法回收子进程的PBC):
myproc.c代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int ret = fork();if(ret == 0){int count = 5;while(count){count--;printf("我是一个子进程,我的PID是:%d\n",getpid());sleep(1);}}else{// father 进程while(1){printf("我是一个父进程, 我的PID是:%d\n", getpid());sleep(1);}}return 0;
}

运行效果:
当子进程运行结束,父进程还没有结束
在这里插入图片描述
在这里插入图片描述
状态变成了Z,< defunct >表示无用的

内存泄漏

简单聊一下内存泄漏:
如果进程X了,还会有内存泄漏吗?
答案是:如果程序退出了,那new/malloc的那些空间都会被自动回收,此时就不存在内存泄漏。
但是,大部分的软件都是一直运行的,是常驻内存的软件(比如操作系统),这时候就要考虑内存泄漏问题。
你可以会想到自己的电脑关机,然后操作系统也关了,但是服务器上的呢?

内核结构申请

Linux中有一个用来专门储存已经无用的task_struct的链表(相当于:数据结构对象的缓存),这样就可以加速未来申请同类对象的速度。


(6)孤儿状态

孤儿进程:当子进程没执行完,父进程已经执行完,先退出了。

这时候子进程的PPID变成1,这个1号进程就是操作系统,也就是说孤儿进程被操作系统领养了。
为什么要领养?因为不领养就没有父进程帮它释放PBC了,那就会内存泄漏。
同时注意:领养以后的子进程就变成后台进程了Ctrl+c就杀不了了)

演示(让父进程运行完,子进程孩子还在运行):
myproc.c文件代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int ret = fork();if(ret == 0){while(1){printf("我是一个子进程,我的父进程PID是:%d\n",getppid());sleep(1);}}else{// father 进程int count = 3;while(count){printf("我是一个父进程, 我的PID是:%d\n", getpid());sleep(1);count--;}}return 0;
}

运行效果:
在这里插入图片描述
在这里插入图片描述
并且这时候,子进程被领养以后就跑后台去了,在终端Ctrl+c杀不掉
只能:kill -9 78793杀掉

终端,前台与后台

进程状态有+的是前台进程,当我们执行命令(进程)时,在后面+ &代表在后台执行。

  • 终端:一个你与计算机的“对话窗口”
  • 前台:前台进程是在终端前台运行的进程,它会占用当前终端的输入输出。
  • 后台:一般与终端分离,即使关闭了启动它的终端,后台进程也可以继续运行。

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

相关文章:

  • JavaScript 一维数组转不含零的两个数
  • Keil MDK 编译问题:last line of file ends without a newline
  • 理解 React 的 useEffect
  • 线性回归之正则化(regularization)
  • Pandas数据可视化
  • 中科院:LRM在简单问题上缺失快思考能力
  • 抽象工厂模式及其在自动驾驶中的应用举例(c++代码实现)
  • Vivado中Tri_mode_ethernet_mac的时序约束、分析、调整——(五)调试注意的问题
  • Java编程基础(第一篇:变量)
  • prim最小生成树+最大生成树【C++】板子题
  • 【Sa-Token】学习笔记05 - 踢人下线源码解析
  • STM32嵌入式
  • JUC复习及面试题学习
  • OpenCV基础01-图像文件的读取与保存
  • 高并发场景下重试策略的演进设计
  • 谷歌相机最新版:专业摄影,一键掌握
  • 基于 Spring Boot 瑞吉外卖系统开发(五)
  • typeScript基础(类型)
  • 2025年人工智能指数报告:技术突破与社会变革的全景透视
  • 011数论——算法备赛
  • 消费维权周报丨上周合同纠纷类投诉多,合同未到期关闭门店等
  • 北理工再通报:开除宫某党籍,免去行政职务,解除聘用关系
  • 在没有穹顶的剧院,和春天的音乐会来一场约会
  • 中国正在俄罗斯国内生产武器?外交部:坚决反对无端指责和政治操弄
  • 鲁比奥称美国已向各方提出了“持久和平的框架”
  • 中马签署互免签证协定,飞往马来西亚的机票搜索量日环比增长超1倍