Linux进程状态
进程正在运行时,需要等待某件事完成,主动放弃 CPU、停下来不运行,进入等待状态,这就叫进程阻塞。进程运行中缺资源、等 IO、等事件,主动放弃 CPU 进入等待,就是进程阻塞;事件结束再变回就绪,重新排队上 CPU。进程挂起:由用户或操作系统,把一个进程暂时暂停,暂停后进程不参与CPU调度,暂时不运行。而且会把进程数据从内存移到外存(硬盘),腾出内存空间。
进程状态基本概念
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状 态(在Linux内核里,进程有时候也叫做任务)。
进程状态本质是Linux内核源代码中task_struct内部的一个整型变量。
/*
*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 运行态既包含正在 CPU 上运行的进程,也包含排队等着 CPU 的就绪进程。
S 可中断睡眠大部分后台程序、服务都处在这个状态。没事就休眠,来了信号或事件立刻唤醒继续执行。
D 不可中断睡眠一般是磁盘读写、硬件交互时进入。为了保护数据安全,不允许中途被打断,只能自己完成 IO 后自动退出。
T 停止态按
Ctrl+Z把程序挂起,就进入 T 状态,暂停运行。Z 僵尸态子进程跑完退出了,代码数据都释放了,只剩 PCB 信息没被父进程回收。不占 CPU 内存,但占用进程号资源,太多会影响系统。
单独说说t状态和X状态:
X 状态:死亡 / 已退出状态
- 含义:进程已经彻底终止,PCB 也被回收,系统中不再存在该进程。
- 特点:
- 这个状态在
ps命令中几乎看不到,因为进程一旦进入X状态,就会被内核从进程列表中清除。 - 它更多是内核内部的一个状态标记,用来表示进程已经消亡,资源已释放。
- 这个状态在
- 大白话理解:就像人已经彻底去世、户口也被注销了,在系统里查不到任何记录了。
t 状态:被跟踪 / 被调试状态
t 是 T(停止态)的一个特殊子状态,全称是 traced。
- 含义:进程正在被调试器(比如
gdb)跟踪,处于暂停状态。 - 场景:
- 用
gdb调试程序时,进程会被gdb暂停,进入t状态。 - 此时进程不能被其他信号唤醒,只能由调试器控制恢复执行。
- 用
- 和
T的区别:T:被普通暂停信号(如SIGSTOP)挂起,收到SIGCONT就能恢复。t:被调试器跟踪暂停,只能由调试器恢复,普通信号无法唤醒。
暂停恢复:kill 对应目标
进程状态转换关系图

进程状态的变化,表现之一就是要在不同的队列中流动,本质上是数据结构的增删查改。
进程状态查看方式
ps aux / ps axj 命令
• a:显示一个终端所有的进程,包括其他用户的进程。
• x:显示没有控制终端的进程,例如后台运行的守护进程。
• j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息。
• u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等。

进程调度
CPU 时间的 “公平裁判”
你可以把进程调度器想象成一个 CPU 时间的裁判,它的任务是让系统在多进程并发时,既高效又有序地运行。图中列出了它的核心目标:
| 目标 | 通俗解释 |
|---|---|
| 公平性 | 每个进程都能分到合理的 CPU 时间,不会出现 “饿死” 的情况。 |
| 高效性 | 调度本身的开销要尽可能小,进程切换要快,不浪费 CPU 时间。 |
| 实时性 | 保证高优先级的实时进程能被及时响应,比如音视频播放。 |
| 负载均衡 | 在多核 CPU 上,把进程均匀分配到各个核心,避免有的核心忙死、有的闲死。 |
正是因为调度器的存在,哪怕系统里有一个死循环的进程,它也无法独占 CPU,你的操作系统依然能流畅运行其他程序。
调度队列:用链表管理所有待运行进程
1. 为什么需要调度队列?
每个进程在内核中都有一个 task_struct 结构体(也就是我们说的 PCB),里面记录了进程的所有信息。既然每个进程的结构都一样,操作系统就可以把它们组织成一个双向链表,形成调度队列。
2. 链表结构是怎么工作的?
每一个进程都有其对应的task_struct,且都是完全一样的,那么这就意味着:我们是否能把这么多个task_struct通过链表的方式连在一起?事实上,操作系统确实是这么做的。
就像下面图里画的,每个 task_struct 里都有 *next 和 *prev 指针:

*next:指向队列里的下一个进程*prev:指向队列里的上一个进程
操作系统调度器只需要遍历这个链表,就能找到所有处于 ** 就绪态(R)** 的进程,然后按照调度算法(比如时间片轮转、优先级调度)选择下一个要运行的进程。
进程调度的本质,就是操作系统通过调度队列管理所有就绪进程,然后按照公平、高效、实时的目标,给每个进程分配 CPU 时间片,实现 “宏观并行、微观串行” 的多进程并发。
进程阻塞
一、什么是进程阻塞
进程正在运行时,需要等待某件事完成,主动放弃 CPU、停下来不运行,进入等待状态,这就叫进程阻塞。
二、什么时候会阻塞
进程遇到不能立刻完成的事,就会阻塞:
- 等待键盘输入、等待鼠标操作
- 读文件、写磁盘、网络收发数据
- 等待别的进程发信号、等待资源
- 调用 sleep ()、wait () 这类函数
三、阻塞后发生什么
- 进程立刻让出 CPU,不再占用时间片
- 从 运行态 → 阻塞态(S/D)
- 退出 CPU 调度队列,不参与 CPU 争抢
- 原地等待事件完成(IO 就绪、有数据、时间到)
进程被阻塞的时候,其task_struct会从CPU中拿下来,进入对应设备的等待队列。
例如当键盘输入数据后,系统会自动唤醒等待队列中的进程,将内核缓冲区中的数据拷贝到用户空间的缓冲区(例如 scanf 提供的变量地址),然后继续运行。
当然,等待队列中的进程肯定不止一个,系统会唤醒队列最前面的进程。
四、两种阻塞状态
- S 可中断阻塞:可以被信号唤醒(大部分普通等待);
- D 不可中断阻塞:正在硬件 IO,不能被杀死(例如银行取款)、不能被信号打断。
五、一句话总结进程阻塞
进程运行中缺资源、等 IO、等事件,主动放弃 CPU 进入等待,就是进程阻塞;
事件结束再变回就绪,重新排队上 CPU。
进程挂起
一、什么是进程挂起
进程挂起:由用户或操作系统,把一个进程暂时暂停,暂停后进程不参与CPU调度,暂时不运行。
而且会把进程数据从内存移到外存(硬盘),腾出内存空间。

二、为什么要进程挂起
- 内存不够用系统内存紧张,把暂时不用的进程挪到硬盘,给别的进程腾内存。
- 用户手动暂停终端按
Ctrl+Z,手动把前台进程挂起。 - 系统负载控制系统太忙,暂时挂起一些后台低优先级进程。
- 调试程序gdb 调试时把进程挂起暂停。
三、挂起后进程处于什么状态
对应 Linux 状态:
- T 停止态:普通挂起(Ctrl+Z)
- t 跟踪停止态:被 gdb 调试挂起
四、挂起的特点
- 进程暂停执行,不再占用 CPU
- 不参与进程调度
- 进程数据可被移到外存,释放内存
- 不会自动恢复,必须人为唤醒
- 进程还在,只是 “休眠冻结” 了
五、怎么挂起、怎么恢复
1. 手动挂起
前台运行程序,按:
Ctrl + Z
进程立刻被挂起,进入 T 状态。
2. 恢复运行
fg # 放到前台继续跑
bg # 放到后台继续跑
六、进程阻塞和进程挂起最大区别
- 阻塞:进程自己主动等 IO、等事件,事件到自动唤醒,还在内存里。
- 挂起:别人强制暂停,不会自动醒,可被移出内存,必须手动恢复。
| 对比点 | 进程阻塞 | 进程挂起 |
|---|---|---|
| 发起方 | 进程自己主动进入 | 系统 / 用户强行暂停 |
| 原因 | 等 I/O、等数据、等资源、sleep | 内存不够、人为暂停、系统负载调控 |
| 所处状态 | S 可中断睡眠 / D 不可中断睡眠 | T 停止态 |
| 内存位置 | 还在内存里 | 换到硬盘外存,释放内存 |
| 唤醒方式 | 事件完成自动唤醒 | 必须手动恢复(如 fg、SIGCONT) |
| 是否占 CPU | 不占 CPU,不参与调度 | 不占 CPU,不参与调度 |
| 目的 | 提高 CPU 利用率,不瞎等 | 缓解内存紧张、人为管控进程 |
挂起的本质就是把进程唤入唤出到磁盘的swap分区。
僵⼫进程
•僵死状态(Zombies)是⼀个⽐较特殊的状态。在子进程退出之后,父进程获取子进程之前信息之前的状态,子进程退出之后是不能把自己全部信息抹除,必须让父进程知道。如果父进程一直不管,不回收,不获取子进程的退出信息,那么Z会一直存在,既当前进程的PCB一直存在,PCB还在会引发内存泄漏。
• 僵死进程会以终⽌状态保持在进程表中,并且会⼀直在等待⽗进程读取退出状态代码。
• 所以,只要⼦进程退出,⽗进程还在运⾏,但⽗进程没有读取⼦进程状态,⼦进程进⼊Z状态。
创建一个维持30秒的僵死进程例⼦:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id = fork();
if (id < 0)
{
perror("fork");
return 1;
}
else if (id > 0)
{ // parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else
{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(0);
}
return 0;
}
查看它:
机器1上运行test:

机器2上查看对应进程pid:

僵⼫进程危害
• 进程的退出状态必须被维持下去,因为他要告诉关⼼它的进程(⽗进程),你交给我的任务,我 办的怎么样了。可⽗进程如果⼀直不读取,那⼦进程就⼀直处于Z状态?是的!
• 维护退出状态本⾝就是要⽤数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中, 换句话说,Z状态⼀直不退出,PCB⼀直都要维护?是的!
• 那⼀个⽗进程创建了很多⼦进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数 据结构对象本⾝就要占⽤内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置 进⾏开辟空间!
• 内存泄漏?是的!
⾄此,值得关注的进程状态全部讲解完成,下⾯来认识另⼀种进程。
孤⼉进程
• ⽗进程如果提前退出,那么⼦进程后退出,进⼊Z之后,那该如何处理呢?
• ⽗进程先退出,⼦进程就称之为“孤⼉进程”
• 孤⼉进程被1号init/systemd进程领养,当然要有init/systemd进程回收喽。
来段代码测试一下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
if (id < 0)
{
perror("fork");
return 1;
}
else if (id == 0)
{ // child
printf("I am child, pid : %d\n", getpid());
sleep(30);
}
else
{ // parent
printf("I am parent, pid: %d\n", getpid());
exit(0);
}
return 0;
}
1号机器上运行代码:

2号机器查看子进程状态:

我们发现子进程被1号进程领养了,就由1号进程回收资源。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)