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

