【进程状态】目录
- 前言:
- ---------------进程状态---------------
- [1. 什么是进程状态?](#1. 什么是进程状态?)
- [2. 进程状态有哪些?](#2. 进程状态有哪些?)
- [3. 怎么理解内核链表是如何设计的?](#3. 怎么理解内核链表是如何设计的?)
- [4. 真实的操作系统Linux的进程状态是什么样的?](#4. 真实的操作系统Linux的进程状态是什么样的?)
-
- [一、运行状态 <---> 运行状态 + 就绪状态](#一、运行状态 <---> 运行状态 + 就绪状态)
- [二、睡眠状态 <---> 阻塞状态](#二、睡眠状态 <---> 阻塞状态)
-
- [① 可中断睡眠状态](#① 可中断睡眠状态)
- [② 不可中断睡眠状态](#② 不可中断睡眠状态)
- (1)睡眠与阻塞核心区别在于?
- (2)小故事:行长问罪------不可中断睡眠的由来!
- (3)不可中断睡眠神秘之谜*
- [三、停止状态<---> Linux特色状态](#三、停止状态<---> Linux特色状态)
-
- [① 停止状态](#① 停止状态)
- [② 跟踪停止状态](#② 跟踪停止状态)
- [四、僵尸状态<---> Linux特色状态](#四、僵尸状态<---> Linux特色状态)

往期《Linux系统编程》回顾:/------------ 入门基础 ------------/
【Linux的前世今生】
【Linux的环境搭建】
【Linux基础 理论+命令】(上)
【Linux基础 理论+命令】(下)
【权限管理】/------------ 开发工具 ------------/
【软件包管理器 + 代码编辑器】
【编译器 + 自动化构建器】
【版本控制器 + 调试器】
【实战:倒计时 + 进度条】/------------ 系统导论 ------------/
【冯诺依曼体系结构 + 操作系统基本概述】/------------ 进程基础 ------------/
【进程入门】
前言:
hi~,小伙伴们大家好啊, (´∀`)♡
今天是2025年11月17日,星期一,时间过得可真快,一转眼鼠鼠已经走到了大三上半学期的第十二周了。再过5、6周,这半学期就要画上句号了⏳⊙﹏⊙∥
手机上也接连收到了寒潮和大风蓝色预警❄️,天气是越来越冷了,时间也是越来越紧了,不知道大家那边都怎么样了呢?(◕ᴗ◕)
| ---------- 2025 年 11 月17日(九月二十八)周一 |
|---|
大家注意保暖的同时也让我们继续踏上Linux系统的学习之旅吧!
今天我们来学的是 【进程状态】:🎉٩(◕‿◕)۶🎉
进程状态:将会脱离教材中对进程状态的抽象解释,通俗生动的介绍Linux中具体的进程状态 ₍₍ ◝(・ω・)◟ ⁾⁾
---------------进程状态---------------
1. 什么是进程状态?
进程状态:是操作系统内核对进程当前活动情况的描述。
- 它用于反映进程正在做什么、是否可被调度执行等状态信息
- 内核通过管理进程状态,实现对 CPU 等资源的高效分配和调度
2. 进程状态有哪些?

从上面的图示中我们可以看到,进程的状态种类较多 ,而且这些状态之间是可以相互切换的。
在众多进程状态中,我们主要学习
运行、阻塞和挂起这三种状态,因为这三种状态是 Linux 系统中最主要的进程状态。
一、运行
进程的运行状态:它反映了进程在 CPU 调度层面的活跃程度。进程处于运行状态时,有两种可能的情况:
- 进程正在 CPU 上执行指令:真正占用 CPU 资源,进行计算、逻辑处理等操作
- 进程处于 "就绪队列" 中:已经准备好执行,只要操作系统的调度器为它分配 CPU 时间片,就能立即被调度到 CPU 上执行
简单来说 :运行状态是进程 "有机会、有能力" 使用 CPU 执行任务的状态,是进程活跃性的核心体现之一。
二、阻塞
进程的阻塞状态:它反映了进程因等待某个特定事件完成,暂时失去了 "使用 CPU 执行任务的能力",即使操作系统的调度器分配 CPU 时间片,该进程也无法立即执行。
- 进程处于阻塞状态时,核心表现为
"等待事件、无法就绪"

我们可以用 C 语言中
scanf的场景来直观理解进程的阻塞状态:当程序运行到scanf语句时 ,会暂停执行并等待用户的键盘输入,这时程序对应的进程就处于阻塞状态。
这一过程的底层逻辑是这样的:每个硬件设备(比如:键盘、鼠标、磁盘等)在操作系统中都对应一个 "等待队列"
当 CPU 执行到
scanf语句时,操作系统会检测键盘的状态 ------ 如果此时没有任何按键被按下,意味着进程需要的资源(输入数据)尚未就绪,继续占用 CPU 只会浪费资源。这时,操作系统会做出两个关键操作:
- 将该进程从 CPU 的运行队列中移除,暂时剥夺其 CPU 使用权
- 把进程 "挂" 到键盘设备的等待队列中,让它在那里等待所需的输入事件
此后,这个进程就进入了阻塞状态,不再参与 CPU 调度,程序也就表现为 "卡住" 的状态 ------ 这就是我们直观感受到的 "等待输入时程序没反应"。
那么请问,当用户按下键盘时,这个阻塞中的进程会主动 "知道" 吗?
其实不会。即便它正在等待输入,进程本身也无法感知硬件状态的变化。真正的流程是:
- 当键盘被按下时,硬件会产生一个中断信号,操作系统作为硬件的 "总管家" 会第一时间捕获这个信号,知道 "键盘输入已就绪"
- 随后操作系统会从键盘的等待队列中找到正在等待输入的进程,将它从阻塞状态唤醒,重新标记为 "就绪" 状态,并把它移回 CPU 的就绪队列中
这样,当 CPU 下次调度时,这个进程就有机会重新获得 CPU 时间片,继续执行
scanf后续的逻辑(读取输入数据并处理)
三、挂起
进程的挂起状态:它表示进程被临时从正常的执行流程中暂停,并且通常会被转移到外存(如:硬盘)中,以释放内存资源,便于操作系统调度其他更需要资源的进程。
- 处于挂起状态的进程,其程序、数据以及相关的进程控制块(PCB)等信息,部分或全部被移至外存中保存,暂时无法参与 CPU 的调度执行
- 只有当满足特定条件后,进程才会被重新调回内存,并恢复执行
产生原因:
- 内存资源紧张:当系统内存中运行的进程过多,内存资源不足时,操作系统为了保证系统的正常运行,会选择一些暂时不活跃的进程,将它们挂起,转移到外存,从而释放出内存空间供其他更急需的进程使用。
- 用户或系统干预:
- 用户可能根据自身需求,手动挂起某个进程,比如:在任务管理器中暂停某个后台程序
- 操作系统也可能基于系统管理策略,对某些进程进行挂起操作,例如:当系统进入节能模式时,挂起一些非关键进程
① 阻塞挂起状态
在操作系统中,磁盘上有一个专门的分区叫做 swap 分区(交换分区),它本质上是一块被划出来用作 "临时内存" 的磁盘空间。
swap 分区的大小通常建议设置为物理内存的 1.5 倍到 2 倍(具体可根据系统需求调整),其核心作用是:在物理内存资源紧张时,临时存放从内存中 "挪出" 的进程数据。
当系统物理内存严重不足时,操作系统会主动排查当前进程的状态,优先盯上那些处于阻塞状态的进程 ------ 因为这些进程本就因等待外部事件(如:键盘输入、I/O 完成)而无法执行,暂时用不到 CPU 和内存资源。
操作系统会对这些阻塞进程执行 "换出" 操作:
- 将进程的代码段、数据段、完整转移到 swap 分区中存储
- 仅在物理内存中保留该进程的PCB(进程控制块) ------ 因为 PCB 体积小,且记录着进程的 ID、状态、挂起前的上下文等关键信息,便于后续恢复
此时,这些被 "换出" 到磁盘的阻塞进程就进入了 阻塞挂起状态
它们既不占用物理内存,也不会参与 CPU 调度,相当于 "暂时休眠" 在 swap 分区中,为其他急需内存的活跃进程腾出了空间。
当阻塞进程等待的事件终于发生(比如:用户按下了键盘),操作系统会立即执行 "换入" 操作:
- 从 swap 分区中将该进程的代码和数据重新加载回物理内存
- 结合保留的 PCB 恢复进程的上下文信息
- 最后将进程从 "阻塞挂起" 转为 "就绪" 状态,放入 CPU 的就绪队列中,等待调度执行
这里需要明确一个关键概念:"挂起" 的核心是 "位置转移"------ 即把进程的核心数据从内存转移到磁盘(swap 分区),而非简单的状态暂停

② 就绪挂起状态
如果系统内存紧张到极致,即便将所有阻塞进程都 "换出" 到 swap 分区后,内存空间仍不足以支撑活跃进程的运行,操作系统就会采取更激进的策略:盯上 CPU就绪队列中的进程。
- 就绪队列中的进程本是 "准备好了执行,只等 CPU 时间片" 的活跃进程
- 但为了保证系统不崩溃,操作系统会选择就绪队列中优先级较低、或排在队列末端(暂时轮不到调度)的进程,同样将它们的代码和数据 "换出" 到 swap 分区,仅保留 PCB
这些被临时 "挪出" 内存的就绪进程,就处于 就绪挂起状态
它们虽然仍有执行资格,但因核心数据在磁盘中,无法直接参与 CPU 调度。
- 当系统内存资源得到释放(比如:某个大进程执行完毕并释放内存),操作系统会根据调度策略,将就绪挂起队列中优先级较高的进程 "换入" 回物理内存
- 恢复其 "就绪" 状态,重新加入 CPU 就绪队列,等待获取时间片后执行
简单来说:swap 分区是内存的 "备胎",而 "阻塞挂起" 和 "就绪挂起" 都是操作系统在内存不足时的 "应急策略"------ 通过将暂时不用或优先级低的进程 "存" 到磁盘,换取系统的稳定运行,待资源充足后再恢复这些进程的执行。
3. 怎么理解内核链表是如何设计的?
核心是理解 "侵入式链表" 的思想 ------ 把链表节点嵌入到数据结构体中,而非让数据结构体依附于链表
一、普通链表 vs 内核链表(侵入式链表)
先看普通链表 的典型结构(类似图中上方的
struct Node):
cstruct Node { int data; // 数据域:存储节点自身的数据 struct Node *prev; // 指针域:指向前一个节点 struct Node *next; // 指针域:指向下一个节点 };普通链表的特点是 "数据 + 链表指针" 紧耦合 ------ 每个节点既存数据,又存链表的前后指针。
这种设计的问题是:如果有多种不同的结构体(如:
struct task_struct表示进程、struct file表示文件)都要组织成链表,每种都得单独实现一套链表逻辑,代码冗余且不通用。
二、内核链表的核心:struct list_head
Linux 内核为了解决这个问题,设计了侵入式链表 ,核心是
struct list_head结构体:
c// 内核链表的"节点"结构:仅包含前后指针,不包含数据 struct list_head { struct list_head *prev; struct list_head *next; };然后,把
struct list_head嵌入到任意需要链表组织的结构体中 (比如:表示进程的struct task_struct):
cstruct task_struct { // 进程的核心属性(简化示意) pid_t pid; int priority; // ... 其他属性 ... // 嵌入链表节点:用于接入"就绪进程链表" struct list_head run_list; // 嵌入链表节点:用于接入"父子进程链表" struct list_head child_list; // 嵌入链表节点:用于接入"等待I/O的进程链表" struct list_head io_wait_list; };这种设计的核心是 "链表逻辑与数据逻辑解耦"
list_head负责链表的 "连接"task_struct负责存储进程的业务数据且一个
task_struct能同时通过不同的list_head接入多个链表。

三、偏移量:从 list_head 反向找到 task_struct
由于
list_head是嵌入在task_struct内部的,内核需要一种方法:通过list_head的指针,反向计算出它所属的task_struct的起始地址。这依赖于 "编译期确定的偏移量":
编译时,编译器会计算出
list_head成员在task_struct中的 "偏移量"(即该成员相对于task_struct起始地址的字节差)运行时,只要拿到
list_head的指针,就能通过指针运算得到整个task_struct的地址
c// 伪代码:通过 list_head 指针找到所属的 task_struct struct task_struct *task = (struct task_struct *) ((char *)list_head_ptr - offsetof(struct task_struct, list_head_member));
四、多链表共存:一个进程,多链管理
图中多个
struct task_struct实例,每个内部都嵌入了多个list_head,这意味着:
- 每个进程可以同时属于多个不同的链表(比如:既在 "就绪进程链表" 中等待 CPU 调度,又在 "父子进程链表" 中记录家族关系)
- 每个
list_head都独立维护自己的双向链表(next/prev指针只连接同用途的list_head)

Linux 内核通过 "侵入式链表" 设计,实现了 "一套链表逻辑,管理所有结构体":
- 无需为每种结构体(如:进程、文件、设备等)单独实现链表
- 一个结构体可同时接入多套链表,满足复杂的管理需求
- 通过偏移量计算,轻松实现 "从链表节点到完整结构体" 的反向查找,让链表操作与数据访问无缝衔接
这种设计极大提升了内核代码的复用性与灵活性,是内核数据结构设计的经典范例。
4. 真实的操作系统Linux的进程状态是什么样的?
下面这些状态是在 Linux 内核源代码中定义的:
cpp
/*
* 任务状态数组是一种特殊的"位图",用于表示进程睡眠的原因
* 因此,"运行中"的状态值为 0,而其他状态可以通过简单的位测试来组合判断
*/
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):并不意味着进程一定正在 CPU 上运行,它表明进程要么正在 CPU 上执行,要么处于运行队列里(即处于就绪状态,等待被 CPU 调度执行)
- S(睡眠状态,sleeping):意味着进程在等待某个事件完成(这里的睡眠有时也被称为可中断睡眠(interruptible sleep)
- 比如:进程在
等待 I/O 操作完成、等待信号量等资源,在等待期间会暂时让出 CPU,当等待的事件发生时,进程会被唤醒并进入运行状态- D(磁盘睡眠状态,Disk sleep):有时候也被称为不可中断睡眠状态(uninterruptible sleep)
- 处于这个状态的进程通常在等待 I/O 操作的结束,比如等待磁盘读写完成
- 在这种状态下,进程不会响应外部的中断信号,只能等待 I/O 操作本身完成后才能被唤醒,这是为了保证 I/O 操作的完整性和数据一致性
- T(停止状态,stopped):可以通过向进程发送
SIGSTOP信号来让进程进入停止状态。
- 被暂停的进程可以通过发送
SIGCONT信号来让进程继续运行- 例如:在终端中按下
Ctrl + Z可以将前台进程暂停,使其进入停止状态,之后若想恢复进程运行,可使用相关命令发送SIGCONT信号- t(跟踪停止状态,tracing stop):主要用于调试场景。
- 当进程被调试器(如:
gdb)跟踪时,可能会进入这种状态,以便调试器对进程进行单步调试等操作
- X(死亡状态,dead):表示进程已经结束,相关的资源已经被完全回收,在系统中几乎不会停留,所以一般通过
ps等命令很难观察到处于该状态的进程。- Z(僵尸状态,zombie):进程已经终止(比如:
代码执行完毕或者被信号杀死),但它的进程控制块(PCB)还没有被父进程通过wait()系列函数回收。
- 僵尸进程会保留少量信息(如:进程 ID、退出状态等),直到父进程处理后,其 PCB 才会被释放
- 如果系统中存在大量僵尸进程,会占用进程 ID 等系统资源,需要避免这种情况
一、运行状态 <---> 运行状态 + 就绪状态

在 Linux 系统中,使用
ps ajx命令查看进程状态时,输出结果中的STAT列代表进程的状态:
S:表示进程处于可中断睡眠状态 。在这个状态下,进程正在等待某些事件的发生。
- 在本程序中,虽然程序看起来在不断输出内容,但实际上程序执行时并非全程占用 CPU,当需要等待外部资源或事件时,进程会主动释放 CPU 资源,进入 "睡眠" 以避免浪费资源,等待下一次 CPU 调度来继续执行循环体,即便看起来在 "持续输出",进程也会在每次输出间隙短暂睡眠
+:表示该进程是前台进程组 的一部分。
- 在 Linux 中,前台进程组是指当前在终端中占用输入输出的进程组,用户可以通过终端对其进行直接交互(比如:使用
Ctrl+C发送中断信号来终止进程)综上所述 :进程状态为
S+则反映了该进程处于可中断睡眠状态且属于前台进程组的特性。

疑问:为什么注释掉printf语句后,运行该程序时,其对应的进程的状态就变成了R+?
- 由于printf 语句被注释,程序进入死循环后,没有涉及到等待 I/O 操作需要进入睡眠状态的情况(比如:向终端输出内容时需要等待终端设备准备好接收数据)
- 此时进程会不断地占用 CPU 资源,
一直在 CPU 上运行或者在就绪队列中等待调度(因为 CPU 可能同时被其他进程占用), 所以进程处于运行状态,显示为R
二、睡眠状态 <---> 阻塞状态
进程的阻塞状态:在 Linux 系统中对应可中断睡眠状态(S,Interruptible Sleep)和不可中断睡眠状态(D,Uninterruptible Sleep),是进程在等待特定事件发生时所处的一种状态
① 可中断睡眠状态
可中断睡眠状态(S)
- 含义:
- 进程因为等待某个事件(如:I/O 操作完成、特定信号到达、获取某种资源等)而暂停执行,进入睡眠状态
- 在这种状态下,进程会让出 CPU 资源,以便其他进程可以获得 CPU 时间片来执行
- 特点:
- 进程对信号是响应的,当
等待的事件发生或者接收到特定的信号(如:SIGKILL、SIGCONT等)时,进程会被唤醒,并从阻塞队列转移到就绪队列,等待被 CPU 调度执行- 例如:一个进程调用
read函数读取磁盘文件内容,由于磁盘 I/O 操作相对较慢,在数据读取完成之前,进程会进入可中断睡眠状态,此时如果收到SIGCONT信号,进程可能会被唤醒 ,但如果是读取数据的事件完成了,也会唤醒进程- 场景:
- 常见于网络请求、磁盘读写、等待用户输入等场景
- 比如:一个网络爬虫程序在发起网络请求获取网页数据时,在数据返回之前,该请求对应的进程会进入可中断睡眠状态,直到服务器返回数据后才会被唤醒继续执行

② 不可中断睡眠状态
(1)睡眠与阻塞核心区别在于?
不可中断睡眠状态(D)
- 含义:
- 同样是进程在等待某事件发生而暂停执行,但与可中断睡眠状态不同的是,处于不可中断睡眠状态的进程
不会响应信号,只有当它等待的事件发生后才会被唤醒- 这种状态通常用于进程在等待一些关键的、不能被打断的系统资源或操作完成,以保证数据的一致性和操作的完整性
- 特点:
- 主要用于内核态中处理一些对硬件设备的访问,如:磁盘 I/O 操作、等待硬件设备完成特定任务等
- 例如:当进程正在进行磁盘写入操作时,为了防止数据丢失或写入不完整,在写入操作完成之前,进程会处于不可中断睡眠状态,此时即使收到信号也不会被唤醒,直到磁盘写入操作彻底完成
- 场景:
- 主要出现在与硬件设备交互密切的场景中,比如:在文件系统中对磁盘进行格式化、读写磁盘元数据等操作时,相关进程会进入不可中断睡眠状态,以确保操作顺利进行
阻塞状态是操作系统实现并发处理的重要机制,它使得进程在等待资源或事件时不会占用 CPU 资源,从而提高了系统整体的资源利用率和运行效率。
同时,可中断和不可中断睡眠状态的区分,也保障了系统在不同场景下的稳定性和数据安全性。
(2)小故事:行长问罪------不可中断睡眠的由来!
进程朝着磁盘喊话:"这里有 100M 的数据,麻烦你帮我存起来。"
磁盘探出 "脑袋",像是刚被唤醒,揉了揉 "眼睛" 回应道:"行是行,但你得等我一下,别着急走。毕竟写入过程中可能会失败,比如:磁盘空间满了之类的情况,我也没法提前预料。不过不管成功还是失败,我都会告诉你结果,再由你去告知用户操作是成功还是失败。"
进程听后,觉得在此期间确实没什么别的事可做,只能干等,于是便进入了睡眠状态。
没过多久,操作系统从进程身边路过,问道:"进程,你在这儿干什么呢?" 进程回答:"我正等磁盘把数据写完,好把结果告诉用户。" 操作系统面露难色:"你没看到我忙得团团转吗?现在内存空间严重不足,能腾的地方我都腾了,可还是不够用。" 说完,操作系统就把这个进程 "杀死" 了。由于进程受操作系统管控,只能 "自杀" 退出。
结果,磁盘在写到 90M 数据时,发现磁盘空间满了,写入失败,赶忙朝着进程大喊:"100M 数据写入失败了,你快告诉用户!进程,你还在吗?" 可进程已经 "死" 了,磁盘拿着这 100M 数据,不知道该如何是好。重写是写不进去了,还有其他进程等着让它写数据,它忙得不可开交,最后只能把这 100M 数据丢弃。
就这样,从系统层面来看,100M 的数据被丢掉了,而且用户还毫不知情。
要是这 100M 数据是银行一天的转账记录,那对银行来说,损失可就大了。
银行行长把进程、磁盘和操作系统都叫到了办公室。行长开口问道:"这次事故,你们三个谁来承担责任?"
行长先看向磁盘,磁盘赶忙说:"行长,您别看着我呀,您又不是不知道,我就是个'打杂'的,人家让我干啥我就干啥。我都跟进程说了,让他一定要等我,可最后我写入失败时他不在了,我有什么办法呢。"
行长接着看向进程,进程理直气壮地说:"您看我干什么?我才是受害者呢。我在等待队列里好好等着,突然来了个'不长眼'的把我杀了,我跟它理论不过,也打不过它,只能乖乖退出了。"
行长最后看向操作系统,操作系统解释道:"行长,您知道我对您是最忠心的。您当初赋予了我权力,当内存空间严重不足时,我有权'杀'进程。而且如果今天我不'杀'这个进程,要是因为资源不够导致我操作系统崩溃,那会有几百个进程结束,丢失将近一个 G 的数据呢。"
行长听完他们三个的话,发现每个人说的都有道理,难道错的是自己吗?
最后,银行行长决定:"数据丢了就丢了吧。从今天开始,进程你新增一个状态,叫**'不可中断睡眠'**。处于这个状态时,你有权对任何想要'杀'你的操作不做任何响应。要是进程处于这个状态,操作系统,你就没权'杀'它了。" 操作系统答应道:"好的。" 进程也说道:"这还差不多。"
所以 :从此之后,在对像磁盘这类关键数据存储设备进行高 IO 操作时,进程的状态不再设为
S(可中断睡眠),而是设为D(不可中断睡眠)
(3)不可中断睡眠神秘之谜*
当进程处于不可中断睡眠状态时,你只能等待该进程自己醒来,或者对操作系统进行重启操作。但有时候,即便重启系统也无法 "杀掉" 处于这种状态的进程,只有通过断电的方式才能将其终止。
- 不可中断睡眠状态在系统中是很少出现的,要是你的进程中出现了不可中断睡眠状态,而且持续时间达到秒级,那基本上就意味着你的计算机快要出问题了
- 因为这很可能是你的磁盘已经老化了,比如:使用了 5 年以上的磁盘,在进行 I/O 操作时容易出现异常,进而导致进程长时间处于不可中断睡眠状态
三、停止状态<---> Linux特色状态
进入停止状态的原因
- 收到特定信号:最常见的是进程接收到
SIGSTOP信号(编号为 19)和SIGTSTP信号(编号为 20)
SIGSTOP是无条件停止进程,且该信号不能被捕获、阻塞或忽略SIGTSTP通常由终端产生,比如用户在终端中按下Ctrl+Z组合键 ,就会向当前前台进程组中的所有进程发送SIGTSTP信号,使它们进入停止状态- 被调试器控制:当使用调试器(如:
gdb)对进程进行调试时,调试器可以向进程发送控制信号,使进程在特定的断点处或满足特定条件时进入停止状态,方便调试人员检查进程的内存状态、变量值、调用栈等信息
- 例如:在
gdb中设置断点后,当程序执行到断点处,进程就会停止
停止状态进程的特点
- 不参与 CPU 调度:处于停止状态的进程不会被操作系统的 CPU 调度器选中并分配 CPU 时间片,因此不会在 CPU 上执行指令,不会占用 CPU 资源,直到进程被恢复执行
- 资源占用相对稳定:进程在进入停止状态时,会暂停当前的执行操作,但它所占用的系统资源(如:内存、打开的文件描述符等)并不会被立即释放,操作系统会保留这些资源,以便进程恢复执行时能继续正常运行
- 可恢复性:
- 停止状态并非进程的最终状态,进程可以被恢复到其他状态(如:运行状态或就绪状态)
- 向处于停止状态的进程发送
SIGCONT信号(编号为 18),可以将其唤醒,使其重新进入就绪队列,等待 CPU 调度执行
① 停止状态
停止状态

假设在终端中运行一个长时间执行的程序
code.exe:
- 当按下
Ctrl+Z组合键时,myprogram对应的进程就会收到SIGTSTP信号,进入停止状态- 此时可以查看进程状态,会看到其状态显示为
T
如果想要恢复该进程的执行。可以使用:
fg命令(将停止的前台进程恢复到前台运行)bg命令(将停止的前台进程恢复到后台运行)它们本质上是向进程发送了
SIGCONT信号。

② 跟踪停止状态
跟踪停止状态

四、僵尸状态<---> Linux特色状态
(1)小故事:警察办案------僵尸状态的由来
今天早上你正在户外跑步,途中从你身旁忽然飞快地跑过一个人。就在你继续往前跑时,突然看到前方约 50 米处,那个人 "扑通" 一声直直倒在了地上。你心里一紧,立刻加快脚步跑过去查看,发现他已经没有了呼吸。
你来不及多想,马上掏出手机拨打了 110 报警。没过多久,警察就赶到了现场。他们会不会一来就说 "赶紧把人抬走"?当然不会。警察做的第一件事,是迅速拉起警戒线封锁现场,防止无关人员破坏可能存在的证据;紧接着联系他的家属告知情况,同时通知法医到场 ------ 因为必须先明确他的死因:是自杀、他杀,还是突发疾病导致的自然死亡?这些关键信息都需要通过现场勘查和法医鉴定来确认。
在这个人倒下死亡后,到法医完成现场采样、家属赶来将遗体接走之前,他一直躺在原地等待 "处理" 的这段时间,就可以类比为进程的 "僵尸状态"------ 虽然 "生命活动" 已经停止,但还需要等待 "负责方"(警察、法医、家属)完成必要的信息确认和后续处理,不能直接 "清理"。
而当法医采集完有效证据、家属将遗体正式接走后,意味着所有 "后续流程" 已完成,这个状态就相当于进程进入了 "死亡状态",彻底从现场(系统)中消失。
(2)关于僵尸状态的双重追问
很多人会有疑问:直接用 "死亡状态" 不就够了吗?Linux 系统为什么还要单独设计一个 "僵尸状态"?
答案的核心,其实藏在父子进程的协作逻辑里
- 我们创建子进程的根本目的,是让它完成特定任务(比如:执行一个命令、处理一段计算、完成一次 I/O 操作)
- 而任务完成得怎么样(是成功执行、异常报错,还是被信号终止),父进程必须知道
这就决定了子进程 "退出" 不能是 "一键清除",而需要一个过渡状态来留存关键信息 ------ 这就是僵尸状态存在的意义。
具体来说,当子进程执行完任务并退出时,Linux 系统会做两件关键操作:
释放 "非必要资源":子进程的代码段、数据段、堆栈等内存资源会被立即释放。
- 因为它已经不会再被 CPU 调度执行,这些资源留着只会浪费内存,不如还给系统供其他进程使用
保留 "核心关键信息":子进程的PCB(进程控制块)会被完整保留。
- PCB 里记录着子进程的
退出状态码(比如:"0" 表示成功,非零表示失败原因)退出信号(比如:是否被SIGKILL强制终止)CPU 使用时间等核心信息 ------ 这些正是父进程需要的 "任务完成报告"而从
子进程退出、系统释放其 代码/数据,到父进程调用wait()/waitpid()函数从 PCB 中读取退出信息的这段时间里,子进程就处于"僵尸状态"它的本质是 :进程实体已消亡,但 "任务结果凭证"(PCB)仍在,等待父进程确认接收
如果没有僵尸状态,直接让子进程退出后进入 "死亡状态"(即:立即释放包括 PCB 在内的所有资源),会出现什么问题?
父进程将彻底无法获取子进程的执行结果
- 比如无法判断子进程是正常完成任务,还是因为权限不足、资源不够而失败
- 这会导致父子进程的协作逻辑断裂:父进程发起了任务,却永远不知道任务的执行情况,后续也无法根据结果调整自身逻辑(比如:子进程失败时父进程重新发起任务、子进程成功时父进程继续下一步操作)
简单说 :僵尸状态就是 Linux 为父子进程设计的 "结果交接缓冲区":
- 它既避免了子进程退出后资源的无效占用
- 又通过保留 PCB 确保父进程能拿到 "任务完成报告"
最终实现了 "资源高效回收" 与 "进程间信息同步" 的平衡 ------ 这正是它无法被 "死亡状态" 替代的核心原因。

(3)僵尸进程造成的内存泄露问题
僵尸进程:处于僵尸状态的子进程是僵尸进程。
- 如果父进程一直对僵尸进程不管不顾,既不回收,也不获取子进程的退出信息,那么僵尸进程就会一直存在,进而引发内存泄漏问题
这里有个知识点需要思考:进程都已经退出了,内存泄漏问题还存在吗?
僵尸进程本身不会像常规程序中由于动态内存分配未释放等原因导致典型的 "内存泄露" (持续占用堆内存等用户可操作的内存空间且无法回收)但从系统资源管理的角度,它会造成内存相关的不良影响(进程控制块(PCB)占用)
- 当子进程进入僵尸状态时,其大部分资源(如:代码段、数据段、堆栈等占用的内存)会被释放,但是 PCB 仍然会保留在系统中
- 虽然单个 PCB 占用的内存空间相对较小,通常是几十到几百字节不等 ,但如果系统中存在大量的僵尸进程,这些 PCB 占用的内存总量就会不断累积
- 当内存资源紧张时,这些被僵尸进程 PCB 占用的内存无法被其他进程利用,从而造成了内存资源的浪费,从宏观上看类似于一种内存资源被 "泄露" 的情况
- 其实,常驻内存的进程(比如:后台服务、守护进程这类长时间运行的程序)如果出现内存泄漏,才是最麻烦的------ 因为它们会持续占用资源,逐渐拖垮系统
- 反而,普通短生命周期的进程,退出时系统会自动回收大部分资源,问题相对没那么突出
那操作系统为什么不主动回收僵尸进程的
task_struct(进程控制块,PCB 的核心载体)呢?
- 这是因为
task_struct里保存着子进程的退出状态(比如:是正常结束,还是因信号终止)等关键信息,操作系统需要把这些信息完整地交给父进程- 要实现 "信息交付",就必须一直维护
task_struct不释放,直到父进程主动来获取。也正因为如此,"回收僵尸进程、避免内存泄漏" 的责任,就落到了开发者身上
(4)僵尸进程的对立面------孤儿进程
孤儿进程:当一个子进程还在运行时,它的父进程却提前终止了,此时这个子进程就会成为孤儿进程。
- 由于父进程已经不存在,没有父进程来对其进行管理和回收资源,系统会自动将孤儿进程的父进程 ID(PPID)更改为 1,也就是让 init 进程(在较新的 Linux 系统中,通常是 systemd 进程,其进程 ID 也为 1 )成为孤儿进程的新父进程
- init 进程会负责对孤儿进程进行后续的资源回收等管理操作
产生原因:
- 父进程异常终止:父进程由于
程序崩溃、收到致命信号(如:SIGKILL)等原因突然结束运行,而此时它创建的子进程可能还在执行任务,没有来得及完成,这种情况下子进程就会变成孤儿进程- 父进程正常退出:在一些程序设计中,父进程完成了自身的任务后正常退出,而子进程可能还需要继续运行一段时间来处理其他工作,这时子进程也会成为孤儿进程
- 比如:父进程负责初始化一些资源并启动子进程来执行特定的长期任务,之后父进程完成初始化工作后退出,子进程继续执行后续任务,从而成为孤儿进程

我们之前常说:"只要登录 Linux 系统,就会有一个 bash 进程为我们创建",那到底是谁创建了这个 bash 呢?
答案是 "系统",而这里的 "系统",本质上就是 Linux 中的1 号进程(在传统系统中是 init 进程,现代系统中多为 systemd 进程)------ 它是系统启动后创建的第一个用户态进程,负责初始化系统服务、管理后续进程,堪称系统进程的 "总管家"。
有人可能会问:"既然有 1 号进程,那有没有 0 号进程呢?"
其实是没有持续存在的 0 号进程的
- 0 号进程是系统启动初期的 "临时进程"(通常是 idle 进程或 swapper 进程),主要负责初始化内核环境
- 当系统完成基础启动、1 号进程成功创建后,0 号进程的使命就结束了,相当于被 1 号进程 "接替" 了系统初始化的核心角色
疑问:1 号进程为什么要 "领养" 孤儿进程?如果不领养会怎样?
所谓 "领养",就是当一个子进程的父进程意外终止时,1 号进程会主动接管这个失去父进程的子进程,成为它的新父进程。
这么做的核心目的,就是避免子进程退出后变成僵尸进程
- 如果不领养,这个子进程就成了 "无主进程",既没有原父进程、也没有新父进程来调用
wait()系列函数回收它的退出状态- 它的 PCB(进程控制块)就会一直残留系统中,变成僵尸进程,浪费系统资源
简单说:在 Linux 系统中,能 "管" 子进程退出回收的只有两方:
- 一是子进程的原父进程(天然的管理者)
- 二是1 号进程(系统层面的 "兜底管理者")
这就像现实世界里,孩子的成长首先由家人负责;如果家人无法照料,政府就会出面接管,确保孩子得到妥善安置 ------1 号进程的 "领养",就是系统层面的 "兜底保障"。
1 号进程领养孤儿进程后,这个孤儿进程通常会转变为后台进程,不再与原终端进行交互绑定。
"后台运行进程" 的概念 :平时我们在终端中用
./自己的命令 &这样的格式启动程序时,就是主动将进程放到后台运行。这种主动启动的后台进程,与孤儿进程转变而来的后台进程,在运行特性上是一致的:
- 不受 Ctrl+C 影响:因为后台进程不处于当前终端的 "前台进程组",而
Ctrl+C发送的中断信号(SIGINT)只会作用于前台进程组的进程,所以用Ctrl+C无法终止后台进程- 仍可输出信息到前台:后台进程虽然在后台运行,但默认情况下仍会将
标准输出(如:printf 打印的内容)和标准错误信息输出到当前终端界面,不会因为在后台就停止消息打印- 需用信号命令终止:如果要结束后台进程,需要先通过
ps或jobs命令找到它的进程 ID(PID),再用kill -9 [进程ID]发送强制终止信号(SIGKILL),才能将其彻底杀死 ------ 这是终止后台进程最常用且有效的方式


