【Linux】Linux进程状态深度解析

【往期Linux回顾】:

/------------Linux入门篇-----------/

【 Linux 历史溯源与指令入门 】

【 Linux 指令进阶 】

【 Linux 权限管理 】


/------------Linux工具篇------------/

【 yum + vim 】

【 sudo白名单配置 + GCC/G++ 】

【 自动化构建:make + Makefile 】

【 倒计时 + 进度条 】

【 Git + GDB调试器 】


/------------Linux进程篇-------------/

【 冯诺依曼体系 + 操作系统 】

【 进程概念 + PID + fork函数 】


目录

一、什么是进程状态

1、进程状态概念

2、进程状态有哪些?

【进程状态转换示意图(理论模型)】

[< 1 > 运行状态](#< 1 > 运行状态)

[< 2 > 进程转换(运行态 -> 就绪态)](#< 2 > 进程转换(运行态 -> 就绪态))

[< 3 > 阻塞状态](#< 3 > 阻塞状态)

[< 4 > 挂起状态](#< 4 > 挂起状态)

[① 阻塞挂起](#① 阻塞挂起)

[② 就绪挂起](#② 就绪挂起)

二、Linux进程状态

1、R(运行状态,running)

2、S(睡眠状态,sleeping)

问题:为啥bash始终处于S状态?

[3、D(磁盘休眠状态,disk sleep)](#3、D(磁盘休眠状态,disk sleep))

【不可中断睡眠小故事】

【浅睡眠和深度睡眠对比】

4、T(停止状态,stoped)

【kill场景演示】

【gdb场景演示】

[【t(跟踪停止状态,tracing stop)】](#【t(跟踪停止状态,tracing stop)】)

5、X(死亡状态,dead)

6、Z(僵尸状态,zombie)

【僵尸状态由来小故事】

【问题】:为什么进程终止后不直接清理,反而会进入僵尸状态?

【僵尸进程演示】

【问题】:为什么父进程不会进入僵尸状态?

【僵尸进程危害】

7、孤儿进程

【孤儿进程演示】


咱们今天来聊聊 Linux 进程的状态 ------ 这是理解系统运行的基础,但其实这些状态就像进程的 "工作模式":有的在全力运行,有的在等待资源,有的暂时 "待命"。

本文会从实际场景出发,拆解 Linux 中常见的进程状态(比如 R、S、D 等),以及它们背后的内核逻辑,帮你搞懂进程在系统里到底是怎么 "干活" 的。

一、什么是进程状态

1、进程状态概念

进程状态操作系统内核对进程当前活动与执行状态的描述,是内核调度资源的管理依据。

  • 它既反映进程的实时状态(如是否在使用 CPU、是否等待硬件 / 文件资源、是否被暂停),也帮助内核判断如何高效分配 CPU 等系统资源,是进程管理的核心基础之一。

2、进程状态有哪些?

【进程状态转换示意图(理论模型)】

(注:不同操作系统的实际实现会有差异,以下是通用理论状态)

  • 新建:进程刚被创建,正在加载到内存的过程中(完成内存分配、代码 / 数据加载),待加载完成后会进入内存并加入就绪队列。
  • 就绪:进程已准备好执行,等待 CPU 调度
  • 运行:进程正在 CPU 上执行指令
  • 阻塞:进程因等待事件(如硬件资源、信号)暂停,释放 CPU
  • 挂起就绪:就绪进程被换出内存,暂存到外存(需 "换入" 回内存才能继续等待调度)
  • 挂起阻塞:阻塞进程被换出内存,暂存到外存(事件发生后先 "换入" 回内存,再转为就绪)
  • 结束:进程执行完成,资源被系统回收

【状态转换逻辑】:

  1. 新建 → 就绪:进程加载完成
  2. 就绪 ↔ 运行:CPU 调度(就绪进程获得 CPU 转为运行;运行进程时间片超时 / 被抢占,回到就绪)
  3. 运行 → 阻塞:进程等待事件(如请求 IO)
  4. 阻塞 → 就绪:等待的事件发生
  5. 就绪 ↔ 挂起就绪:内存不足时,就绪进程(代码和数据被换出,PCD对象还在内存中)被 "换出" 到外存;外存中进程被 "换入" 回内存
  6. 阻塞 ↔ 挂起阻塞:阻塞进程被 "换出" 到外存;外存中阻塞进程等待的事件发生后,先 "换入" 回内存
  7. 运行 → 结束:进程执行完成

【说明】:

上述是操作系统理论中的全量进程状态,而在 Linux 系统中,运行、阻塞、挂起是最 核心的三种状态(实际会通过更具体的状态标识,如 R、S、D 等,来对应这些逻辑状态)。

接下来,我们就基于进程状态的通用理论模型,来详细解析这三种状态的具体定义与特点

< 1 > 运行状态

进程的运行状态,是衡量它在 CPU 调度环节活跃程度的核心状态。

当进程处于这一状态时,会呈现两种具体情形:

  1. CPU 执行中:已经获取到 CPU 的使用权,正在借助 CPU 完成运算、逻辑处理等实际任务;
  2. 就绪队列待命 :所有运行前的资源准备都已完成,只要操作系统的调度器为它分配 CPU 时间,就能马上切换到 CPU 上开始执行(这个时候实际上进程处于就绪态)。

运行状态(CPU 执行中)是进程 "正在使用 CPU 资源" 的状态,而就绪态(队列待命)是进程 "具备执行条件、等待获取 CPU 资源" 的状态,二者共同构成了进程活跃性的核心体现。


< 2 > 进程转换(运行态 -> 就绪态)

进程被调度到 CPU 上运行后,不会一直执行到完毕 (比如while(1);这类无限循环代码,也不会独占 CPU),核心原因是 "时间片" 机制:

  1. 时间片的定义:每个进程会被分配一个固定时长的 CPU 使用权(比如10ms);
  2. 时间片的作用:时间片耗尽后,当前运行态的进程会被从 CPU 上 "拿下来",回到就绪队列(对应 "我已经准备好了,可以随时被调度" 的状态);
  3. 最终效果:通过 "进程切换"(频繁将进程在 CPU 和就绪队列间切换),在一个时间段内,所有进程的代码都会被轮流执行,实现并发执行

< 3 > 阻塞状态

*阻塞状态:*进程因等待某类资源 / 事件(如 I/O 操作、信号、锁)而无法继续执行的状态:

  1. 进入条件:运行态的进程在执行中需要等待资源(比如读取磁盘文件、等待网络数据),会主动放弃 CPU,由运行态转为阻塞态;
  2. 状态特征:处于阻塞态的进程,即使 CPU 空闲也无法被调度执行(因为缺少必要资源),其 PCB 会被移至 "阻塞队列"(而非就绪队列);
  3. 退出条件:当进程等待的资源 / 事件就绪(比如文件读取完成),会从阻塞态转为就绪态,PCB 被移回就绪队列,等待 CPU 调度。

简言之,阻塞状态是进程 "不具备执行条件(缺资源)、无法使用 CPU" 的状态,是进程非活跃性的典型体现。


< 4 > 挂起状态
① 阻塞挂起

阻塞挂起是挂起状态的核心子状态之一,指进程因等待资源 / 事件处于阻塞态时,又因系统内存不足,其代码和数据被换出到外存(如磁盘)的状态:

  1. 进入条件:系统内存紧张时,会将阻塞队列中的进程(移动的是代码和数据,并且会在PCB中标记移到的位置)换出到外存,转为阻塞挂起状态;
  2. 状态特征:进程既缺少执行所需的资源(符合阻塞态的条件),同时代码和数据也不在内存,双重 "无法执行";
  3. 退出条件
    • 先等进程等待的资源 / 事件就绪:阻塞挂起态转为就绪挂起态;
    • 再等系统内存充足:由就绪挂起态换入内存,恢复为就绪态。

swap 分区是磁盘上的存储空间,在阻塞挂起场景中,它会暂存进程的代码和数据(此时进程 PCB 仍留在阻塞队列),是内存不足时实现进程挂起与恢复的关键载体。


② 就绪挂起

就绪挂起是挂起状态的子状态,指进程本身已具备执行条件(符合就绪态要求),但因系统内存不足(且阻塞进程已被优先挂起),其代码和数据被换出到外存(如 swap 分区),PCB 仍保留在就绪队列的状态:

  1. 进入逻辑:系统会优先将阻塞态进程挂起(释放内存);若内存仍不足,才会将就绪队列中暂未被调度的进程(就绪态)的代码和数据换出到外存,转为就绪挂起状态(PCB 留在就绪队列,标记数据外存位置);
  2. 状态特征:进程 "能执行但没内存",需等系统内存充足后,代码和数据从外存换入内存,恢复为就绪态;
  3. 核心逻辑:"先阻塞挂起、后就绪挂起" 是因为阻塞进程本就无法执行,挂起它们的内存成本更低,是系统优先保障资源利用率的策略。

二、Linux进程状态

下面的状态是在Linux内核源代码中定义的:

cpp 复制代码
/*
* 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)

**定义:**进程已具备执行条件,正在等待 CPU 调度,或正在 CPU 上执行

  • R 状态 = 运行状态 + 就绪状态
  • Linux 中用 "R 状态(运行状态)" 统一指代 "可执行的进程状态"(不管是正在执行还是等待调度),不再单独区分 "运行态" 和 "就绪态",而是通过 "是否在 CPU 上执行" 来区分 R 状态的两种场景。

【测试1】:

​​​​​​

启动这个程序后,发现它的进程状态一直是阻塞类状态(对应 Linux 中的 S 态)。这是为什么呢?

因为我们一直在用printf函数(向显示器输出内容),进程会进入显示器的等待队列,等待 IO 资源就绪。这个操作会让进程处于 "等待 IO 资源" 的阻塞状态,而 CPU 处理速度极快,进程实际占用 CPU 的时间极短,所以大多数时间都处于阻塞状态,呈现出持续阻塞的状态。


  • S(Sleeping) 进程处于可中断休眠状态,此时它没在运行,而是等待某个条件满足(比如等待键盘输入、磁盘读写完成、sleep定时器到期),这个状态下不会占用 CPU 资源。
  • +(Foreground) 表示进程是当前终端的前台进程:
    • 终端的所有输入(包括按Ctrl+C发送中断信号)都会直接作用于它,能直接中断进程;
    • 它会占据当前终端,你需要等它运行结束,或者按Ctrl+Z将其暂停并切换到后台(切换后进程状态会变成S且不带+),才能在同一个终端里执行其他命令。
  • 无+(Background) 表示进程是当前终端的后台进程:
    • 终端的输入(包括Ctrl+C)不会影响它,无法直接用Ctrl+C中断;
    • 若要操作它,需要用fg命令将其切回前台,或用kill命令发送信号中断;
    • 也可以在启动进程时直接加&(例如./test &),让进程一开始就在后台运行。

【测试2】:

去掉printf后,进程状态始终是R+,这是因为代码里没有了等待 IO 的操作,进程会一直占用 CPU 执行无限循环,所以持续处于运行态(R) ;同时它是前台进程(+),所以状态显示为R+

2、S(睡眠状态,sleeping)

定义: 意味着进程在 等待事件完成 (这里的睡眠有时候也叫做 可中断睡眠 (interruptible sleep ))

运行上述程序,你会发现该进程几乎一直处于休眠状态 ------ 这是因为代码里循环执行的printfsleep操作,都会让进程进入阻塞(休眠)状态:

  • printf需要向终端输出内容,进程会阻塞等待 IO 资源就绪;
  • sleep会让进程主动休眠指定时长,期间同样处于阻塞状态。

这两种操作都会让进程暂时让出 CPU,所以它大部分时间都处于休眠(阻塞)状态。

问题:为啥bash始终处于S状态?

bash 始终处于 S 状态,是因为它在等待用户输入命令,此时处于 IO 阻塞的休眠状态。

3、D(磁盘休眠状态,disk sleep)

D 状态 (磁盘休眠状态,disk sleep)是 Linux 进程的一种阻塞状态,指进程因等待磁盘 IO 操作完成而进入的休眠状态。

  • 进程在等待磁盘读写(比如从硬盘读取文件、向硬盘写入数据),此时会暂停执行、不占用 CPU;
  • 属于不可中断睡眠(与 S 态的 "可中断" 不同)------ 进程在等待磁盘 IO 期间,不会响应普通的信号(比如Ctrl+C),只能等磁盘操作完成后才会被内核唤醒。
【不可中断睡眠小故事】

进程凑到磁盘跟前,递过一摞数据:"劳烦帮存 1000M,存好存坏都跟我说声哈!" 磁盘擦了擦磁头应下,进程闲着没事,往旁边一躺就睡了 ------ 这是能被叫醒的 "可中断休眠"(S 态)。

存到一半,操作系统攥着内存统计单急慌慌跑过来,瞅见睡着的进程就皱了眉:"内存都快挤爆了,腾地方!" 话没说完,直接把进程 "清" 没了。

这边磁盘吭哧吭哧存到 900M,突然弹出 "空间不足" 的提示 ------ 存失败了。它扭头就喊进程报信,可喊破喉咙,进程早没了踪影。1000M 数据没处交代,只能跟着 "蒸发" 了。


这数据要是银行一天的转账记录,损失可就大了!行长把进程、磁盘、系统都叫到办公室:磁盘先摆手:"我按进程说的存了,最后找不着它,我有啥办法?"进程委屈巴巴:"我睡得好好的,莫名其妙就没了,我才是冤大头!"系统也喊冤:"内存都不够撑到下一秒了,不杀它系统崩了,损失更大!"


行长听完叹了口气,拍板定了规矩:"以后进程等磁盘干活时,就睡'叫不醒的觉'------ 叫'不可中断休眠'(D 态)。这时候谁都不能动它,必须等磁盘把活儿干完、把信儿传给它,才能接着来!"

打这儿起,进程再等磁盘这类关键设备干活时,就会钻到 D 态的 "保护壳" 里 ------ 既能安心等结果,也能护着数据不丢啦。
不可中断睡眠(D 态)是系统里比较 "特殊" 的进程状态:一旦进程进入这个状态,通常只能等它完成磁盘 IO 后自行唤醒,哪怕重启系统都未必能终止它,极端情况下甚至得靠断电才能强制结束。

而且这个状态在正常系统里很少见 ------ 要是你的进程长时间(比如持续几秒)卡在 D 态,基本是计算机出问题的信号,大概率是磁盘老化(比如用了 5 年以上)导致 IO 操作异常,才让进程陷在这个 "醒不来" 的状态里。

【浅睡眠和深度睡眠对比】
维度 浅睡眠(S 态) 深度睡眠(D 态)
触发场景 等待 IO、定时器等(非关键操作) 等待磁盘等关键 IO 操作
信号响应 能被普通信号(如 Ctrl+C)唤醒 不响应普通信号,只能等 IO 完成
系统操作 可被系统杀死(如内存不足时) 系统无法杀死,必须等 IO 结束
作用 灵活等待,可中断 保护关键数据,避免操作中断

4、T(停止状态,stoped)

T状态(停止状态,stopped)是 Linux 进程的一种暂停状态,指进程因接收到暂停信号(如 Ctrl+Z)** 而暂时停止执行,但并未终止

核心特点:


场景 1:gdb 调试暂停

在 gdb 调试中,通过设置断点让进程进入 T 态:

  • 执行break 行号/函数名(比如break main.c:10)设置断点,当程序运行到断点处时,会自动暂停执行,进入 T 态;
  • 此时进程的代码、数据等资源仍保留在内存中,可在 gdb 中进行查看变量、单步调试等操作;
  • 调试完成后,执行 gdb 的continue命令,进程会从断点处恢复运行。

场景 2:信号暂停(如 Ctrl+Z)

用户可通过kill命令向进程发送暂停信号(SIGSTOP),让进程进入 T 态:

  • 执行kill -SIGSTOP 进程ID,进程会暂停运行,内存中的代码、数据等资源保持不释放;
  • 后续若要恢复进程,可执行kill -SIGCONT 进程ID(发送继续信号),进程会从暂停处恢复执行

简单说:T 状态是进程 "被暂停但没结束" 的状态,随时可以恢复运行。

【kill场景演示】

此时进程处于S 态(可中断睡眠),因为它在持续等待 IO 设备输出。

我用kill -19(对应SIGSTOP信号)暂停了进程,此时进程进入T 态(停止状态)

使用kill -18(对应SIGCONT信号)让进程恢复运行,它回到了之前的S 态(可中断睡眠),同时不再保持前台运行状态。

【gdb场景演示】

此时 gdb 进程处于S 态(可中断睡眠),因为它在等待用户输入调试指令。

通过b main打主函数断点、r运行程序后,程序会在断点处暂停 ------ 此时被调试的./test 进程进入 t 态(跟踪停止状态),gdb 进程仍保持 S 态(等待后续调试操作)。

这里的t态是停止状态的细分场景,它专门对应 "进程被调试器(如 gdb)跟踪暂停" 的情况,和普通的 T 态(比如用 Ctrl+Z 挂起的停止状态)在触发场景上有所区别。

【t(跟踪停止状态,tracing stop)】
  • 核心定义: 进程因为被调试器(如 gdb)跟踪而暂停执行的状态。

  • 常见触发方式:

    1. 程序运行到断点 处(break 命令)。
    2. 使用单步执行 命令(next, step)后。
    3. 在程序运行时按下 Ctrl + C 主动中断。
  • 如何恢复:

    • 在调试器中使用 continue, next, step 等命令让程序继续执行。

5、X(死亡状态,dead)

X(dead)是进程彻底终止前的瞬时状态,你不会在ps/top里看到它。

  • 核心特点:仅存在于内核回收进程资源的瞬间,无交互性,资源回收完成后进程直接消失。

6、Z(僵尸状态,zombie)

【僵尸状态由来小故事】

下午三点,你坐在咖啡馆靠窗的位置,刚喝完最后一口拿铁,起身拿起包准备离开。

就在你推门的瞬间,邻桌的客人突然捂住胸口,趴在桌上没了动静。你吓了一跳,立刻喊来店员,同时拨了急救电话。

店员的第一反应不是直接把人抬走 ------ 而是先守在桌边,不让其他客人碰桌上的物品(手机、药瓶),同时等医生和警察到现场。毕竟得先搞清楚:是突发疾病?还是有其他情况?这些 "现场信息" 得留着等专业人员确认。


这时候,这位客人虽然已经没了意识,但还在原地等着 "后续处理"------ 就像进程的僵尸状态: 自身运行已经停止,但 "父进程(系统)" 还没回收它的状态信息,不能直接清理。

等医生做完初步检查、警察记录完现场信息,工作人员才把人抬上救护车 ------ 这个 "信息交接完、彻底离场" 的瞬间,就是进程的死亡状态(X) :所有收尾工作完成,彻底从系统里消失。


总而言之:

  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵尸进程
  • 僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
【问题】:为什么进程终止后不直接清理,反而会进入僵尸状态?

核心原因是父进程需要获取子进程的 "退出状态":

进程终止时,会先将自身的代码、数据、堆栈等内存资源彻底清理,但会把 "运行结果"(正常结束 / 异常崩溃)存在 PCB(进程控制块)中;而父进程需要通过wait()/waitpid()等系统调用读取这个状态,才能判断子进程的工作是否完成。


这就像 "人意外去世后,身体(对应进程的代码 / 数据)会被处理,但现场的证据(对应 PCB 里的退出状态)会被保留"------ 要等警方(对应父进程)来调查取证(读取退出状态),确认死亡原因后,才会彻底收尾(清理 PCB)。


若内核直接清理 PCB,父进程就会丢失子进程的退出信息,相当于 "子进程的工作结果没完成交接"。因此僵尸态是子进程留着 PCB 等父进程 "查收结果" 的临时状态------ 父进程读取状态后,内核才会彻底清理 PCB。

【僵尸进程演示】

进程退出时,若父进程未主动回收子进程信息(未调用wait()/waitpid()),子进程会持续处于僵尸态(Z 状态),其对应的task_struct结构体(进程控制块)无法被内核释放,仅保留退出状态等必要信息,直到父进程处理或自身终止后由 1 号进程接管清理。

【问题】:为什么父进程不会进入僵尸状态?

父进程的父进程通常是终端 Shell(比如 bash)。当我们运行程序时,父进程由 Shell 创建并作为其子进程存在。一旦父进程执行结束或被终止,Shell 会主动调用wait()类函数回收父进程的退出状态和 PCB 资源,不会让父进程残留为僵尸进程。此外,即使父进程意外退出,系统的 1 号进程(init/systemd)也会接管并清理其资源,因此父进程不会像未被回收的子进程那样成为僵尸。

【僵尸进程危害】

僵尸进程的定义

处于僵尸状态(Z 状态)的子进程 ,即子进程已退出,但父进程未调用wait()/waitpid()读取其退出状态时的进程。


僵尸进程的 "内存泄漏" 本质

僵尸进程不会造成常规程序的内存泄漏 (如动态分配内存未释放导致堆内存占用),但从系统资源管理角度,会引发类似内存泄漏的不良影响:

  • 子进程进入僵尸状态时,代码段、数据段、堆 / 栈等用户空间资源会被系统释放
  • 进程控制块(PCB,存储在task_struct结构体中)会被保留,用于存储子进程的退出状态(如正常结束、信号终止等);
  • 单个 PCB 占用内存较小(几十~几百字节),但大量僵尸进程的 PCB 会持续积累,占用的内存无法被其他进程利用,宏观上造成内存资源的 "泄漏"。

不同进程的影响差异

  • 常驻内存进程(如后台服务、守护进程):若产生僵尸进程,会长期占用资源,逐渐拖垮系统;
  • 短生命周期进程:退出时系统会自动回收大部分资源,僵尸进程问题相对不突出。

系统不主动回收 PCB 的原因

task_struct中保存了子进程的退出状态关键信息 ,操作系统需要将这些信息完整交付给父进程;因此必须维护PCB直到父进程主动调用wait()获取信息 ------ 回收僵尸进程的责任由开发者承担。

7、孤儿进程

父进程先于子进程退出 时,子进程会变成孤儿进程(原父进程已消失,子进程 "无父")。

此时,操作系统会将这个孤儿进程的父进程重新设置为 1 号进程(init/systemd,系统初始化进程),由 1 号进程 "领养" 它。

当孤儿进程后续退出并进入僵尸状态(Z 状态)时,1 号进程会主动调用wait()类函数回收它的退出状态和 PCB 资源,避免其残留为僵尸进程。

简单说:父进程提前退 → 子进程变孤儿 → 1 号进程领养 → 子进程退出后由 1 号进程回收,不会成为僵尸进程。

【孤儿进程演示】

从图中可以看出父进程结束后,子进程的PPID就变成1了,这也刚好验证了我们上面说的没问题

相关推荐
凉晓风39 分钟前
Linux中常见几种自启动方式的区别
linux·运维·服务器
小熊officer41 分钟前
Nginx学习
运维·学习·nginx
LCG元1 小时前
考古利器:find 命令的高级用法,按时间、大小、内容精准查找
linux
ManThink Technology1 小时前
LoRaWAN网关:连接私有服务器是“可行”还是“明智”?
运维·服务器
t***82112 小时前
华为数据中心CE系列交换机级联M-LAG配置示例
服务器·华为·php
U***74692 小时前
Linux(CentOS)安装 MySQL
linux·mysql·centos
3***g2052 小时前
Linux系统离线部署MySQL详细教程(带每步骤图文教程)
linux·mysql·adb
J***Q2922 小时前
DevOps金融服务安全要求
运维·安全·devops
Dovis(誓平步青云)2 小时前
《内核视角下的 Linux 锁与普通生产消费模型:同步原语设计与性能优化思路》
linux·运维·性能优化