Linux:进程状态(进程二)

前篇博客我们了解到了进程的概念,这篇博客我们就来谈谈进程的状态,进一步加深对进程的了解

Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态

下图是一个较为完整的状态图,现在还看不懂?没关系,当这篇博客结束肯定就会一目了然啦

1.运行&&阻塞&&挂起

在操作系统理论中,运行、阻塞、挂起是描述进程生命周期的核心状态,它们的划分基于进程是否占用 CPU、是否等待资源、以及进程映像(代码、数据)是否在内存中,下面我们将对这三者进行说明,方便我们更容易搞懂linux进程概念

1.运行

我们把pcb(也就是task_struct)在调度队列 (runqueue)(就绪状态,等待 CPU 调度)里面或者已经在cpu里面运行的状态统称为运行状态(running)

2.阻塞

进程因等待不可立即获得的资源 (如 I/O 完成、信号量释放、键盘输入等),主动放弃 CPU,其 PCB 从调度队列(runqueue)中移除,进入特定的等待队列(如 I/O 等待队列)的状态

比如scanf函数,进程要想继续运行下去,就必须使用键盘输入,如果键盘一直未输入(处于不活跃)就不会运行下去,那系统不可能让该进程一直占着调度队列吧,于是就把他链入键盘的等待队列中,当键盘输入内容时,就会先告诉操作系统,我输入内容啦,操作系统就会检查一下键盘的等待队列中有没有内容,有的话就把他重写链入调度队列里面继续运行

其实运行和阻塞是逻辑上反向的过程:

运行->阻塞:等待资源→资源未准备->进入等待队列→资源就绪→唤醒回调度队列

阻塞->运行:等待队列→资源就绪→回调度队列

3.挂起

当系统内存不足时,内核将进程的映像(代码、数据、堆栈)从内存换出到磁盘(swap 分区),仅保留 PCB 在内存中的状态。挂起状态的进程无法被直接调度,必须先将映像从磁盘换回内存(换入),才能转为运行或阻塞状态

磁盘会专门开一个swap交换分区,大概是内存的1~1.5倍,用于存放因为内存不足而交换的数据和代码

挂起与其他状态的转换:

  • 阻塞 → 挂起:内存不足时,内核将长期阻塞的进程映像换出到磁盘。
  • 挂起 → 阻塞:内存充足时,内核将进程映像从磁盘换入内存,恢复为阻塞状态(仍在等待资源)。
  • 挂起 → 运行:若进程映像换入内存,且等待的资源已就绪,则从挂起状态直接转为运行状态(进入 runqueue)

2.操作系统的无奈之举->挂起与OOM Killer 终止进程(丢弃进程)

假如内存已经超级满了,此时操作系统会直接将某些进程挂起,不然可能会影响所有进程,一般会将等待队列里面的资源swap,就如果这样还是满的,可能会直接将调度队列里面的进程swap,如果磁盘swap分区也满了呢,操作系统为了不影响所有进程,可能会直接将丢弃某些进程,内核会按 "先保核心、后牺牲非核心" 的顺序处理丢弃

3.Linux内核里面的进程状态

了解了基本的操作系统中描述进程生命周期的核心状态,我们来看看 Linux内核源代码怎么说
为了弄明⽩正在运⾏的进程是什么意思,我们需要知道进程的不同状态。⼀个进程可以有⼏个状
态(在Linux内核⾥,进程也叫做任务(翻译问题))
下⾯的状态在kernel源代码⾥定义:

/*
*The task state array is a strange "bitmap" of
*reasons to sleep. Thus "running" is zero, and
*you can test for combinations of others with
*simple bit tests.
*/
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 */
};

  1. R 运行状态(running/runnable)并不意味着进程一定在运行中,它表明进程要么是在 CPU 上执行指令,要么是在运行队列(runqueue)中等待 CPU 调度,是唯一参与 CPU 竞争的状态。
  2. S 睡眠状态(sleeping/interruptible sleep):意味着进程在等待事件完成(如 I/O 就绪、键盘输入、信号量释放),此时进程会从运行队列移除,进入等待队列,可被信号(如 SIGINT)唤醒,是 Linux 中最常见的阻塞状态。
  3. D 磁盘休眠状态(Disk sleep/uninterruptible sleep):进程通常在等待关键 IO 操作完成(如磁盘读写、内存页交换),处于深度阻塞状态,无法被信号唤醒(包括 kill -9),避免关键操作被打断导致数据不一致。
  4. T 停止状态(stopped):可以通过发送 SIGSTOP 信号(强制暂停)或 SIGTSTP 信号(用户 Ctrl+Z 触发)让进程暂停,暂停后可通过 SIGCONT 信号恢复运行,暂停原因是外部信号干预,而非等待资源。
  5. t 被跟踪状态(traced):进程被调试器(如 gdb)跟踪,通常因触发断点而暂停,是 T 状态的特殊子集,需通过调试器命令(如 continue)恢复运行,状态标记为小写 t 以区分普通停止状态。
  6. Z 僵尸状态(Zombie):子进程已终止(代码执行完毕),但父进程未调用 wait ()/waitpid () 函数回收其 PCB(进程控制块),进程不再占用 CPU 和内存资源,仅残留 PCB 结构,过多会耗尽 PID 资源。
  7. X 死亡状态(dead):这个状态只是进程生命周期的最终返回状态,表明进程已完全终止,所有资源(包括 PCB)被内核彻底回收,你不会在任务列表(如 ps 命令输出)中看到这个状态。

下面是这7种状态与基本状态的关系图:

状态类型 包含状态 触发原因 是否等待资源 能否被信号唤醒 核心场景
运行 / 就绪 R 正在执行或等待 CPU 调度 -(本身就是就绪 / 运行) ls、循环程序、调试恢复后
阻塞状态 S、D 等待资源(I/O、信号量等) S 可唤醒,D 不可 等待键盘输入、磁盘 I/O、网络请求
暂停状态 T、t 外部信号(SIGSTOP)或调试 T 可被 SIGCONT 唤醒 Ctrl+Z暂停、gdb 断点
特殊状态 Z、X 子进程未回收(Z)、完全终止(X) Z 需回收父进程,X 无 子进程退出未回收、进程终止

4.通过实验来观察状态

1.R状态

运行状态

运行一下

为什么这里有S+与R+呢,那是因为printf这个过程太快了,于是大部分时间都处在等待状态,如果我们把printf删掉,那么就是一直R+状态

+是什么意思呢,说明该进程在前台 运行,没有+就说明说后台运行

2.S状态

其实就是阻塞状态,最简单的例子就是scanf

此时就是S+状态

3.D状态

这个比较有意思,举个例子,如果一个用户往一个银行打1000块钱,于是用户电脑正在磁盘查找是否有1000块钱,于是该进程就进入了阻塞状态,但是此时电脑内存已满,并且磁盘swap分区也满了,操作系统无奈只能丢弃某些进程,首当其冲的就是阻塞状态的进程,操作系统直接将这个进程丢掉,过了一段时间,磁盘知道了,有这1000块钱,于是告诉进程,可是这个进程已经被抛弃了,磁盘只能拿着这个1000块钱的数据无事可做,但是磁盘要干别的事情,只能也将这个数据抛弃,可是用户是不知道的,因为进程已经没了,没有进程可以告诉用户是否存在1000块钱,于是也就无法进行接下来存钱的操作

可是这个事件里面谁都没有错,操作系统为了一整个进程无奈丢弃S状态进程,进程需要服从操作系统,所以必须被丢弃,磁盘需要干自己的事情,不可能一直等进程

所以为了避免这种情况,于是将内存与磁盘之间的交互从S状态设置为D状态,告诉操作系统不要动这个进程

4.T状态

如果一个进程被我们使用ctrl+z停止,就是T状态

5.t状态

当使用gdb的时候就是t状态,因为此时打了一个断点,断点就是终止程序的嘛,所以就是t

我们用-g编译

打断点,然后r运行,就会发现t状态

6.X状态

由于一个进程结束时(且已被父进程知晓),会直接调用X状态,所以很难观察到,所以这里知道有这个状态就行

7.Z状态

如果一个子进程先退出,而父进程不退出也不接收子进程退出信息(比如告诉父进程我是因为什么退出的,时异常还是正常退出)的话,那么子进程就会进入僵尸状态,也就是Z状态,只有当父进程接收子进程退出信息,子进程才可以变成X状态

发现变成了Z状态

ctrl+Z于是双双变为X状态退出


僵⼫进程危害

进程的退出状态必须被维持下去,因为他要告诉关⼼它的进程(⽗进程),你交给我的任务,我
办的怎么样了。可⽗进程如果⼀直不读取,那⼦进程就⼀直处于Z状态?是的!

维护退出状态本⾝就是要⽤数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,
换句话说,Z状态⼀直不退出,PCB⼀直都要维护?是的!

那⼀个⽗进程创建了很多⼦进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数
据结构对象本⾝就要占⽤内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置
进⾏开辟空间!

内存泄漏?是的!

5.孤儿进程

与僵尸进程相反,孤儿进程顾名思义就是没有父母的进程,也就是父进程比子进程先结束,那次是怎么办呢,如果没人接管他,就会变成僵尸进程,造成内存泄露,所以此时pid为1的进程(可以简单理解为操作系统)接管他了,不仅接管了,而且还自动将其变为后台进程(也就是.cmd &),所以我们无法使用ctrl+c退出,只能使用kill -9 pid来杀死子进程

ctrl+c无法杀死子进程

用kill -9 pid就可以杀死

好啦,这就是有关进程状态的内容啦,我们下一篇博客见>.<

相关推荐
Xの哲學1 小时前
Linux 指针工作原理深入解析
linux·服务器·网络·架构·边缘计算
乌萨奇也要立志学C++1 小时前
【Linux】进程信号(二)信号保存与捕捉全解析、可重入函数、volatile
linux·服务器
行初心2 小时前
uos基础 sys-kernel-debug.mount 查看mount文件
运维
CryptoPP2 小时前
使用 KLineChart 这个轻量级的前端图表库
服务器·开发语言·前端·windows·后端·golang
Ai173163915792 小时前
2025.11.28国产AI计算卡参数信息汇总
服务器·图像处理·人工智能·神经网络·机器学习·视觉检测·transformer
一水鉴天2 小时前
整体设计 定稿 之1 devOps 中台的 结论性表述(豆包助手)
服务器·数据库·人工智能
1***y1782 小时前
DevOps在云中的Rancher
运维·rancher·devops
无垠的广袤3 小时前
【工业树莓派 CM0 NANO 单板计算机】本地部署 EMQX
linux·python·嵌入式硬件·物联网·树莓派·emqx·工业物联网
414a3 小时前
LingJing(灵境):Linux Amd64局域网设备访问靶机教程
linux·安全·web安全·网络安全·lingjing·灵境
tianyuanwo3 小时前
多平台容器化RPM构建流水线全指南:Fedora、CentOS与Anolis OS
linux·运维·容器·centos·rpm