3. 进程状态
目录一、教材中经典的进程状态模型二、调度 运行 阻塞 挂起2.1. 画图 例子2.2. 阻塞与挂起的区别2.3. 上述例子的流程图三、Linux 内核链表 的底层工作原理和内存布局图3.1. 结构定义3.2. 如何通过链表节点找到宿主结构体3.3. 图示可视化四、Linux 的进程状态4.1. Linux内核源代码怎么说4.2. 进程状态查看4.3. 用代码展示不同的进程状态4.3.1. R 运行状态4.3.2. S 睡眠状态 (sleeping)4.3.3. t 状态跟踪停止4.3.4. T 停止状态 (stopped)4.3.5. D 磁盘休眠状态 (Disk sleep)4.3.6. 僵死状态 (Zombies)五、孤儿进程一、教材中经典的进程状态模型这张图详细描述了进程在生命周期中可能经历的各种状态以及导致状态发生转换的条件。图中的主要状态包括创建 (New)进程正在被创建。执行 (Running)进程正在 CPU 上运行图上方有 CPU 指向它。终止 (Terminated)进程执行结束正在被系统回收。活动就绪 (Active Ready)进程准备好运行等待 CPU 调度。静止就绪 (Suspended Ready)进程准备好运行但被挂起换出到外存即使有 CPU 也无法运行需要先“激活”。活动阻塞 (Active Blocked)进程因等待某事件如 I/O而暂停且驻留在内存中。静止阻塞 (Suspended Blocked)进程因等待某事件而暂停且被挂起换出到外存。关键的状态转换过程调度活动就绪 → 执行系统分配 CPU。时间片完执行 → 活动就绪时间片用完被切换下来。事件发生活动/静止阻塞 → 活动/静止就绪等待的事件满足了。挂起 (Suspend)执行/活动就绪/活动阻塞 → 静止就绪/静止阻塞系统把进程调出到外存以释放内存。激活 (Activate)静止就绪/静止阻塞 → 活动就绪/活动阻塞系统把进程从外存调入内存。简单来说这张图展示了一个进程从创建到终止的过程中是如何被操作系统调度、等待资源以及被挂起和激活的。上述的内容是 教材中经典的进程状态模型其中箭头指向是不同状态之间转换的事件发生但是这个过于复杂并且不好理解我们主要讲解的是从内核、数据结构的视角对 不同状态进行理解。二、调度 运行 阻塞 挂起2.1. 画图 例子我们在第一章 --- 初步了解操作系统中我们聊过管理在 内核中是通过双向链表 进行管理的其中上面是一个示意图。进程的调度就是CPU 按照一定的顺序选取调度队列中一个 task_struct 执行。小问题之前不是说 PCB 是由 双向链表 来管理的怎么现在又扯到了 队列这个是设计的原因把PCB设计成了既可以成为一个 全局的双向链表又可以把相关进程的放到一个队列当中换而言之一个 PCB 节点可以是 A 的数据结构又可以是 B 的数据结构。运行进程在调度队列中进程的状态都是 running阻塞等待某种设备或者资源就绪例如键盘、显示器、网卡、磁盘等。运行的时候是存在一个 运行队列阻塞的时候也会存在一个队列是等待队列。这些硬件设备的伪代码可以是这样的struct device{ ind id; in vender; int staurs void*data; struct device*next; struct device*prev; int type; struct task_struct* wait_queue; }里面会有一个 等待队列。我们下面以 按住键盘 这个事件为例进行展开当代码运行到 cin 或者 scanf 的时候会阻塞等待键盘硬件就绪。当我们没有按住键盘的时候称为键盘文件没有就绪。阻塞的话调度队列就会查询为什么阻塞把相应的 PCB节点放到对应硬件设施的PCB的wait_queue 中。按住键盘属于硬件就绪操作系统作为硬件的管理者就会查询对应硬件设备的节点将运行状态设置为activate并且检查等待队列发现指针不为空就把对应的节点的状态改为 运行状态把该进程重新链接回去到 运行队列 里。此时这个进程还没有被调度键盘中的数据没有被读取。在 CPU 的调度之下在运行scanf把数据从设备中去读取到进程的上下文当中然后被我们拿到。还会发生这个情况当我们的内存资源严重不足了有些进程不会被访问但是还站着内存。例如在阻塞队列中的代码和数据就不会被执行。此时磁盘就会把 阻塞队列中的代码和数据放到 swap 交换分区只将 PCB 存下来。此时就是挂起状态内存资源严重不足操作系统做一些页面置换的算法把一些不会被调度的进程或者内存块交换到对应的磁盘上此时进程只有PCB的成为挂起进程。等到有资源了输入内容了操作系统知道了就会把之前把之前的数据和代码 换入到 原来的PCB上2.2. 阻塞与挂起的区别状态是否在运行队列PCB 是否在内存代码/数据是否在内存原因阻塞❌ 不在✅ 在✅ 在等待设备/资源就绪挂起❌ 不在通常✅ 在部分❌ 被换出到交换分区swap内存资源严重不足阻塞 → 挂起当内存不足时操作系统可能将阻塞队列中的进程的代码和数据交换到磁盘swap只保留 PCB此时进程进入挂起阻塞状态。唤醒时需要先换入代码和数据从 swap 读回内存再进入就绪队列。2.3. 上述例子的流程图运行队列 (runqueue)│├─ 进程A (运行) → scanf → 阻塞│ ↓│ 从运行队列移除│ ↓└─ 进程B (运行) 键盘等待队列 (wait_queue)│用户按键 (中断)↓状态 → 就绪↓重新放入运行队列↓调度器选中 → 继续执行三、Linux 内核链表的底层工作原理和内存布局图3.1. 结构定义传统链表结构体 Node 包含 data数据以及next/prev(指针。这种方式导致链表只能存放特定类型的数据通用性差。内核链表单独定义一个通用的 struct list_head只包含 next 和 prev 指针。实际使用你想让哪个结构体变成链表节点就把 struct list_head 作为一个成员嵌入进去如图中间的 struct task_struct 嵌入了 list_head list。3.2. 如何通过链表节点找到宿主结构体因为链表里存储的只是 list_head 的地址当你拿到一个 list 节点的指针时如何反向找到它所属的那个大的 task_struct 结构体呢offsetof 宏和 container_of 宏的底层原理求偏移量利用 (struct task_struct*)0 - links 假设结构体在 0 地址直接访问成员 links 的地址就得到了 links 在 task_struct 中的偏移量偏移地址。反向推导利用 (char*)list - 偏移量用当前 list 的内存地址减去该偏移量就能算出宿主结构体task_struct的起始内存地址。转换类型最后将算出的地址强转为struct task_struct*)就拿到了完整的结构体指针。3.3. 图示可视化红色大框代表一个完整的 task_struct 结构体。蓝色小框代表嵌入其中的 list_head 节点。箭头表示链表前后的指针指向。通过这种方式你可以把任意类型的结构体进程控制块、文件描述符、内存页等用同一套 list_head 逻辑串起来极大提高了代码复用率。四、Linux 的进程状态4.1. Linux内核源代码怎么说static const char *task_state_array[] { R (running), /* 0 */ S (sleeping), /* 1 */ D (disk sleep), /* 2 */ T (stopped), /* 4 */ T (tracing stop), /* 8 */ Z (zombie), /* 16 */ X (dead) /* 32 */ };R 运行状态 (running)并不意味着进程一定在运行中它表明进程要么是在运行中要么在运行队列里。S 睡眠状态 (sleeping)意味着进程在等待事件完成这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep。D 磁盘休眠状态 (Disk sleep)有时候也叫不可中断睡眠状态 (uninterruptible sleep)在这个状态的进程通常会等待IO的结束。T 停止状态 (stopped)可以通过发送 SIGSTOP 信号给进程来停止T进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。X 死亡状态 (dead)这个状态只是一个返回状态你不会在任务列表里看到这个状态。僵死状态 (Zombies)是一个比较特殊的状态。当进程退出并且父进程使用wait()系统调用后面讲没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。t 状态跟踪停止当进程被 ptrace系统调用所跟踪时产生。最典型的场景是被调试器如 gdb 或者系统调用追踪器如 strace挂起。当调试器在断点处命中、或者进行单步调试时被调试的进程就会进入这个t状态4.2. 进程状态查看ps aux / ps axj 命令a显示一个终端所有的进程包括其他用户的进程。x显示没有控制终端的进程例如后台运行的守护进程。j显示进程归属的进程组ID、会话ID、父进程ID以及与作业控制相关的信息u以用户为中心的格式显示进程信息提供进程的详细信息如用户、CPU和内存使用情况等4.3. 用代码展示不同的进程状态4.3.1. R 运行状态R 运行状态 (running)并不意味着进程一定在运行中它表明进程要么是在运行中要么在运行队列里。#include iostream #include cstdio #include unistd.h using namespace std; int main() { int i 0; while (true) { i; } return 0; }在另一个终端输入以下命令用于监视while :; do ps ajx | head -1; ps ajx | grep myprocess; sleep 1; done在这代码中会发现它的状态永远是 R 因为你时刻都在用户态跑 CPU内核永远不会主动让它睡眠不会进入到 内核中如果你是在 循环中使用打印操作如下面的代码就会出现 R 和 S 交替进行。#include iostream #include cstdio #include unistd.h using namespace std; int main() { while (true) { printf(hello linux\n); } return 0; }此时我们发现他的绝大部分时间都是在 S并不是 R。首先需要确定的是这个任务一定是有 R 的状态毋庸置疑但是为什么绝大多数情况下都是 S呢粗略的解释一下这个 printf 操作其实是封装了 系统调用 函数的相当于 会把数据 放入到 内核中如果终端此时正在处理之前的数据、缓冲区已满或者硬件来不及消费write系统调用就会把进程挂起进入 S 状态直到终端驱动程序把数据取走腾出空间。因此会出现 S 的情况。4.3.2.S 睡眠状态 (sleeping)S 睡眠状态 (sleeping)意味着进程在等待事件完成这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep。可以中断浅睡眠#include iostream #include cstdio #include unistd.h using namespace std; int main() { while (true) { sleep(1); } return 0; }4.3.3.t 状态跟踪停止t 状态跟踪停止当进程被 ptrace系统调用所跟踪时产生。最典型的场景是被调试器如 gdb 或者系统调用追踪器如 strace挂起。当调试器在断点处命中、或者进行单步调试时被调试的进程就会进入这个t状态操作方式在 scanf 中打入断点然后使用 gdb 进行调试#include iostream #include cstdio #include unistd.h using namespace std; int main() { printf(我是一个进程,pid:%d\n, getpid()); int x; scanf(%d, x); printf(%d\n, x); return 0; }在 scanf(%d,x); 打上断点此时我们看一下进程状态变化4.3.4.T 停止状态 (stopped)T 停止状态 (stopped)可以通过发送 SIGSTOP 信号给进程来停止T进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。操作方式运行一个进程后按住 ctrl z 即可#include iostream #include cstdio #include unistd.h using namespace std; int main() { while (true) { sleep(1); } return 0; }如果想要杀死这个 myprocess 进程可以使用这个命令kill -9 pid4.3.5.D 磁盘休眠状态 (Disk sleep)D 磁盘休眠状态 (Disk sleep)有时候也叫不可中断睡眠状态 (uninterruptible sleep)在这个状态的进程通常会等待IO的结束。现在有个场景就是 os 中的某个进程要想磁盘写入 100 MB但是呢内存资源太紧张了一直在 sleep 中然后 os 发现了这个进程为节约资源就把这个进程给杀了。万一写入这个100MB的数据是非常关键的呢例如金钱的转入流水信息等如果直接删除了那么谁都不知道这个问题就会造成一系列的后果。所以这类进程是不能删除的哪怕资源很紧张因此这种进程的状态称之为D(disk sleep)不可中断睡眠深度睡眠输入这个命令dd if/dev/zero of~/test.txt bs4096 count10000 oflagdsync命令的含义用全零数据生成一个 40MB 的文件但写入方式极其“较真”——每写 4KB 数据都必须等硬盘物理确认“收到并存好”后才继续写下一批用下面的的命令进行检测while :; do ps ajx | head -1; ps ajx | grep dd | grep -v grep; sleep 1; done4.3.6.僵死状态 (Zombies)僵死状态 (Zombies)是一个比较特殊的状态。当进程退出并且父进程使用wait()系统调用后面讲没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。意思就是子进程已经进行完毕但是父进程一直不管。就会出现僵尸进程代码#include iostream #include cstdio #include unistd.h using namespace std; int main() { pid_t id fork(); if (id 0) { cerr 创建子进程失败 endl; return 1; } else if (id 0) { // child int count 5; while (count--) { sleep(1); cout 我是子进程 当前 pid: getpid() , 父进程id: getppid() count: count endl; } } else { // father while (true) { sleep(1); cout 我是父进程 当前 pid: getpid() , 父进程id: getppid() endl; } } return 0; }实验效果如果父进程一直不管不收回不孩子进程的退出信息那么Z就会一直存在会造成什么问题严重的内存泄漏问题如果进程退出了内存泄露还存在不不存在因为在学习c语言malloc申请空间的时候就算申请空间大退出程序该进程就销毁了就不会出现内存泄漏的时候。那么什么样的进程具有内存泄漏的问题是比较的麻烦的。常驻内存的进程 ---- 长期运行内存泄漏非常危险因为泄漏会不断累积最终导致系统内存耗尽。五、孤儿进程孤儿进程不属于 Linux 进程状态如 R、S、D、Z、T 等它属于一种“进程分类”或“进程场景”。孤儿进程的定义当父进程先于子进程退出被杀死或正常结束了而子进程还在运行那么这个子进程就变成了“孤儿进程”。父进程如果提前退出那么子进程后退出进入Z之后那该如何处理呢父进程先退出子进程就称之为“孤儿进程”孤儿进程被1号init进程领养当然要有init进程回收喽。#include iostream #include cstdio #include unistd.h using namespace std; int main() { pid_t id fork(); if (id 0) { cerr 创建子进程失败 endl; return 1; } else if (id 0) { // child while (true) { sleep(1); cout 我是子进程 当前 pid: getpid() , 父进程id: getppid() endl; } } else { // father int count 5; while (count--) { sleep(1); cout 我是父进程 当前 pid: getpid() , 父进程id: getppid() count: count endl; } } return 0; }使用 下面的命令进行监听while :; do ps ajx | head -1; ps ajx | grep myprocess; sleep 1; done看看结果图在父进程走之后子进程 就会被 1 号进程给 “领养”此时 终端仍然会打印这个语句“我是子进程当前 pid: 789879 , 父进程id: 1” 需要输入 这个命令才可以杀死kill pid那么 1 号进程是什么呢输入以下命令就知道了ps ajx | head -1 ps ajx | grep 1 | head -5 ls -l /sbin/init父子进程关系中如果父进程先退出子进程要被1号进程可以理解成操作系统领养这个被领养的进程子进程叫做孤儿进程。为什么要领养这是因为在父进程结束之后子进程无人看管然后为了防止出现内存泄漏问题就交给系统pid1管着完

相关新闻

Pixhawk遥控输入与电机连接全链路解析:从PWM信号到电调校准

Pixhawk遥控输入与电机连接全链路解析:从PWM信号到电调校准

1. 项目概述:这不是“接上线就飞”,而是飞控与物理世界的第一次真实握手你手里的Pixhawk飞控板,本质上是一台嵌入式飞行计算机——它不长眼睛、不长耳朵,更没有触觉。它所有的感知和决策,都依赖于外部输入信号的精准喂…

2026/7/15 3:31:47阅读更多 →
AI智能体交易的风险与安全实践:从MCP协议到金融数据处理

AI智能体交易的风险与安全实践:从MCP协议到金融数据处理

当你对着手机说一句"买一万块英伟达",AI真的会从你的真实账户里扣款下单——这不是科幻电影,而是Robinhood在2026年5月27日上线的Agentic Trading(智能体交易)功能。这家拥有2700万用户的美国券商,把曾经只有…

2026/7/15 3:31:47阅读更多 →
LaTeX 表格宽度自适应:从单栏到跨栏的精细化调整

LaTeX 表格宽度自适应:从单栏到跨栏的精细化调整

1. LaTeX表格宽度调整的核心挑战在学术写作和文档排版中,表格是展示数据的重要工具。但很多LaTeX新手都会遇到这样的困扰:明明精心设计了表格内容,最终呈现效果却总是不尽如人意——要么表格缩在页面角落显得小家子气,要么内容溢出…

2026/7/15 3:31:47阅读更多 →
EasyX图形编程进阶:从消息循环到双缓冲的交互式窗口框架

EasyX图形编程进阶:从消息循环到双缓冲的交互式窗口框架

1. 项目概述:从控制台到图形窗口的跨越如果你已经跟着上一篇文章,成功地在Visual Studio里配置好了EasyX,并且让那个黑色的控制台窗口弹出来,画出了第一个圆或者一条线,那么恭喜你,你已经推开了图形编程世界…

2026/7/15 6:12:06阅读更多 →
GSV2221@ACP# 在 AI 算力环境完整落地方案(搭配此芯主机 + IX 交换芯片 + 爱芯 NPU)

GSV2221@ACP# 在 AI 算力环境完整落地方案(搭配此芯主机 + IX 交换芯片 + 爱芯 NPU)

IX8012/IX8024:PCIe 交换芯片,负责 CPU、爱芯算力卡、NVMe 存储、网卡之间高速数据总线互联;GSV2221:DP1.4 MST 视频转换 Type-C PD 一体化芯片,负责 AI 设备多屏显示输出、视觉采集回显、交互外设扩展、整机供电管理…

2026/7/15 6:12:06阅读更多 →
FPGA流水线滤波器设计与实现

FPGA流水线滤波器设计与实现

1. FPGA流水线滤波器基础概念我第一次接触FPGA流水线滤波器是在2013年的一个雷达信号处理项目上。当时团队需要处理每秒上亿采样点的数据流,传统串行结构的FIR滤波器根本无法满足实时性要求。这时候,流水线架构就像救命稻草一样出现在我们面前。**流水线…

2026/7/15 6:12:06阅读更多 →
C++ JSON库二进制数据处理:nlohmann/json的binary类型实战指南

C++ JSON库二进制数据处理:nlohmann/json的binary类型实战指南

1. 项目概述:当JSON遇上二进制在C的世界里处理数据交换,JSON格式因其轻量、易读和跨平台特性,几乎成了现代应用开发的“标配”。nlohmann/json库,凭借其直观的API和出色的易用性,更是成为了C社区处理JSON事实上的标准选…

2026/7/15 6:12:06阅读更多 →
原理+实践,深入解析ShardingSphere-JDBC分片算法与基因法

原理+实践,深入解析ShardingSphere-JDBC分片算法与基因法

1. ShardingSphere-JDBC 分片机制基础认知第一次接触分库分表时,我盯着订单表里上亿的数据发愁。单表查询已经慢得像蜗牛,更别提那些需要关联企业信息的复杂查询了。直到遇见ShardingSphere-JDBC,这个藏在JDBC驱动层的神器,用最轻…

2026/7/15 6:12:06阅读更多 →
15A无刷电机FOC控制:硬件选型与算法实现

15A无刷电机FOC控制:硬件选型与算法实现

1. 项目背景与核心挑战在工业自动化、无人机和电动汽车等领域,无刷直流电机(BLDC)因其高效率、长寿命和低维护需求而广受欢迎。然而,实现高性能的BLDC控制并非易事,尤其是当需求达到15A这样的高电流水平时。传统六步换…

2026/7/15 6:07:06阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/14 4:56:14阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/15 6:12:45阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/14 6:17:41阅读更多 →
AI框架决定企业AI能走多远

AI框架决定企业AI能走多远

企业AI建设的第一性原理 企业搞AI,最关键的决定是什么?不是选哪家大模型,不是先做哪个场景,不是招多少AI人才——而是选哪个AI开发框架。 为什么?因为框架决定了企业AI能力的"天花板"。选对了框架&#xff0…

2026/7/15 0:01:30阅读更多 →
Java企业为什么需要AI框架

Java企业为什么需要AI框架

Java企业在AI时代的尴尬处境 Java是全球企业级应用开发的主流语言——全球超过一半的企业系统跑在Java上。但在AI浪潮面前,很多Java企业感到尴尬:大模型的接口是各种语言的,AI开发社区以其他语言为主流,似乎Java在AI时代"掉队…

2026/7/15 0:01:30阅读更多 →
CC3230x嵌入式开发实战:SD主机、定时器与低功耗模式深度解析

CC3230x嵌入式开发实战:SD主机、定时器与低功耗模式深度解析

1. 项目概述:为什么需要关注CC3230x的SD主机、定时器与低功耗?在物联网和嵌入式设备开发领域,我们常常面临一个核心矛盾:设备需要具备强大的连接能力、可靠的数据存储和实时控制功能,同时又必须严格控制功耗以延长电池…

2026/7/15 0:01:30阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/14 15:07:30阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/14 4:45:36阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/14 2:42:17阅读更多 →