前言
在操作系统的世界里,进程的 “生老病死” 并非随机无序,而是被一套精密的状态管理机制所调控。无论是程序从启动到运行的 “活跃期”,因等待资源而进入的 “阻塞态”,还是因内存紧张被暂时换出的 “挂起态”,这些状态的切换背后,藏着操作系统对资源调度、设备管理的底层逻辑。
本文将从理论与实践两个维度拆解进程状态:先从操作系统的通用模型出发,解析 “运行、阻塞、挂起” 三大基础状态的本质 —— 如何通过 PCB(进程控制块)在不同队列间的移动实现状态切换,以及进程调度与设备管理如何通过结构体和队列协同工作;再聚焦 Linux 系统,详细列举其特有的进程状态(如 R、S、D、Z、T 等),结合实例说明每种状态的触发场景、内核逻辑及观察方法。
无论你是想理解操作系统的调度原理,还是想搞懂 Linux 中 “僵尸进程”“不可中断睡眠” 等具体问题,这篇文章都将为你搭建起从抽象理论到具体实现的桥梁,让你对进程状态的认知从 “零散概念” 升华为 “体系化逻辑”。
目录
进程状态(操作系统)
运行&&阻塞&&挂起(概念)
✅ 1、进程调度与设备管理
本质:结构体与队列管理
🧠 2、PCB:进程控制块是调度核心
🏃♂️ 3、运行队列 runqueue
💡 4、状态切换的本质:PCB在不同队列之间移动
🖥️ 5、设备管理结构体 device 与等待队列
🔁 6、状态转换流程图
Linux进程状态
状态列举
详细解释与实例
进程状态(操作系统)
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
进程的状态决定了系统该如何处理进程。在操作系统里,可以用#define,宏定义来定义进程的状态,在task_struct结构体定义整形变量来对应状态。
进程状态就是task_struct中的一个整数。
运行&&阻塞&&挂起(概念)
CPU运行一个进程,先找到PCB,通过PCB找到进程对应的代码和数据。一个CPU。有一个调度队列。
调度队列就是进程状态为 ready 或 running 的“候选人名单”,它决定谁可以被 CPU 执行,而调度器就是挑选“下一个上场的选手”的裁判。
运行队列sruct struct*runqueue,里面有一个个PCB结点。所以PCB既在全局链表也在运行队列中。
运行:进程在调度队列中,进程的状态就是running。
阻塞:等待某种设备或者资源就绪。
操作系统对计算机的软硬件资源进行管理。像键盘,显示器,网卡等设备。操作系统像对这些硬件资源进行管理–先描述在组织。一个一个结构体device串联起来。每个device里面有个等待队列。
挂起:CPU,内存资源比较紧时,将进程的代码和数据放到外设磁盘交换分区中,腾出可用资源。(阻塞挂起和就绪挂起)
✅ 1、进程调度与设备管理
本质:结构体与队列管理
我们可以把操作系统的运行抽象为:
“通过维护多个队列和结构体,实现对 CPU、内存、设备等资源的调度与控制”
🧠 2、PCB:进程控制块是调度核心
struct task_struct {
pid_t pid;
long state; // 进程状态:running、sleeping 等
unsigned long flags;
struct mm_struct *mm; // 指向进程地址空间的指针
struct task_struct *parent;
struct list_head run_list; // 运行队列用的链表节点
…
};
✳ 位置:
-
所有 PCB 放在一个全局链表中,便于进程查找和管理
-
同时活跃进程还会被挂入运行队列 runqueue中,供调度器选择运行
🏃♂️ 3、运行队列 runqueue
struct runqueue {
struct task_struct *curr; // 当前正在运行的任务
struct list_head queue; // 任务链表
int nr_running; // 正在运行的任务数
};
-
操作系统调度器从 runqueue 里选出下一个运行进程,赋值给 CPU 执行。
-
所以 “运行状态” = 在运行队列中 + 被 CPU 选中执行中
💡 4、状态切换的本质:PCB在不同队列之间移动
running | 占用 CPU 正在运行 | PCB 在 runqueue 中,且被调度执行 |
ready | 有资格运行但暂未被调度 | PCB 在 runqueue 中等待 CPU |
sleeping | 等待 I/O(如键盘、网卡等) | PCB 被挂到设备的 wait_queue 上 |
zombie | 运行结束但父进程未回收资源 | PCB 保留在进程表中 |
stopped | 被暂停 | 被挂到某个暂停状态队列(如 ptrace) |
🖥️ 5、设备管理结构体 device 与等待队列
struct device {
int id;
int status; // 是否就绪
void *data; // 设备私有数据
struct device *next; // 串联所有设备
int type; // 类型:键盘、网卡…
struct task_struct *wait_queue; // 等待此设备的进程队列
};
设备访问流程:
进程访问设备但设备未就绪
系统将当前 PCB 加入设备的 wait_queue
将进程状态设为 sleeping
当设备就绪,唤醒 wait_queue 上的进程 → 重新进入 runqueue
🔁 6、状态转换流程图
+———-+ block (I/O wait) +————+
| running | ———————–> | sleeping |
+———-+ +————+
|
| preempt/yield
v
+———-+ wakeup +————+
| ready | <———————- | waiting |
+———-+ +————+
|
| schedule()
v
+———-+
| running |
+———-+
✅ 操作系统本质上是:
“通过结构体组织资源,通过队列调度进程,通过状态切换实现多任务并发。”
进程状态的变化表现之一就是要在不同的队列中进行流动,本质就是就是数据结构的增删查改。把PCB在两个队列中来回串。
Linux进程状态
状态列举
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): 并不意味着进程一定在运行中,它表明进程要么运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这⾥的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个 状态的进程通常会等待IO的结束。
T停⽌状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停⽌(T)进程。这个被暂停的 进程可以通过发送 SIGCONT 信号让进程继续运⾏。
X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表⾥看到这个状态。
详细解释与实例
有+是进程是在前台出现,在命令行。没有+是在后台进行的。
while :; do ps axj | head -n 1; ps axj | grep -v grep | grep code; sleep 1; done
grep -v grep 是一个常用的过滤技巧,用于排除包含 grep 自身的进程。
./code &(在后面加&)
等待键盘输入,S阻塞sleeping 可中断休眠,浅度睡眠,可以杀掉
T,t暂停例子
debug下,gdb对程序断点调试,t进行追踪
ctrl Z 暂停程序
D 深度休眠,代表进程正在等待某个 I/O 操作(如磁盘读写、网络请求、外设交互等)完成,且在此期间无法被中断或杀死。该状态不做演示,一般都是高IO操作,若出现D,就要检查磁盘等设备了。
Z僵尸进程,为了获取退出信息。
在Linux系统里,所有的进程是某个进程的子进程,要么是bash的,要么是自己的,创建子进程的目的,是为了让子进程完成某种事情的。父进程就需要知道完成的结果的相关信息,子进程退出不能释放所有资源,会保留最小化的 PCB 信息,退出的信息暂时维持住,在子进程退出之后,父进程获取信息之前,就要有一个状态就是Z状态。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t id=fork();
if(id<0){
perror("fork");
}
else if(id==0){
int count=5;
while(count–){
printf("我是一个子进程,pid=%d,count:%d.\\n",getpid(),count);
sleep(1);
}
}
else{
while(1){
printf("我是一个父进程,pid=%d\\n",getpid());
sleep(1);
}
}
return 0;
}
当子进程在 5 秒后退出时,由于父进程没有调用wait()或waitpid()来回收子进程的退出状态,子进程会变成僵尸进程(Z 状态)。
如果父进程一直不管,不回收,不获取子进程的退出信息,那么Z一直存在,会引起内存泄漏问题。
进程退出了,内存泄漏问题还在不在?自己代码引起的内存泄漏不存在了。
什么样的进程具有内存泄漏问题是比较麻烦的?常驻内存的进程
结束语
进程状态的管理,本质上是操作系统对 “有限资源” 与 “无限需求” 的动态平衡艺术。从理论层面的 “运行、阻塞、挂起” 模型,到 Linux 系统中精细的状态划分(如 D 状态对 I/O 原子性的保护、Z 状态对父子进程通信的支持),每一种状态的设计都承载着特定的功能目标 —— 既要保证进程高效运行,又要避免资源竞争与数据不一致。
理解进程状态,不仅是掌握 “查看状态”“分析问题” 的实用技能,更能帮我们透过现象看本质:操作系统如何通过 PCB 和队列 “操控” 进程的生命周期?不同状态的转换如何体现 “调度优先” 与 “资源依赖” 的权衡?这些思考,将为我们深入学习进程调度算法、内存管理、设备驱动等知识打下坚实基础。
如果你在实践中遇到了特殊的进程状态问题(如长期 D 状态的排查、僵尸进程的清理),不妨回头再梳理这些理论逻辑 —— 答案往往就藏在状态转换的底层原理中。欢迎在评论区分享你的实践案例,让我们一起在 “理论指导实践,实践反哺理解” 的循环中,深化对操作系统的认知。
评论前必须登录!
注册