进程的状态与转换
这一节的考法非常固定:给出一个状态转换,判断其是否可能发生。所以真正要掌握的不是”有五个状态”这种清单,而是每条边为什么存在、以及那两条不存在的边为什么不存在。
机制
三个基本状态是被”缺什么”分出来的
一个进程在任意时刻,无非三种处境:正在用 CPU、能用但轮不到、想用也没用。这就是三个基本状态:
运行态——正在 CPU 上执行。
就绪态——已经获得了除 CPU 之外的一切所需资源,万事俱备只欠 CPU。
阻塞态——在等某个事件(I/O 完成、拿到某个资源、等一个信号),此时即使把 CPU 白送给它也没用,因为它无事可做。
请注意划分它们的那把尺子只有一把:缺的是不是 CPU。
- 缺 CPU,别的都不缺 → 就绪
- 缺的是别的东西 → 阻塞
- 什么都不缺,正在跑 → 运行
这把尺子能解决绝大多数判断题。比如”进程因为时间片用完被换下来”,它缺的是 CPU,所以进就绪队列而不是阻塞队列。
再加上生命周期两端的创建态(正在申请 PCB 和资源,还没准备好)和结束态(正在善后,PCB 还没回收),就是五状态模型。
四条合法的转换边
flowchart LR C(创建态):::edge --> |"初始化完成"| R R(就绪态):::core --> |"被调度<br/>(唯一入口)"| N(运行态):::core N --> |"时间片到<br/>被抢占"| R N --> |"主动请求<br/>得不到满足"| B(阻塞态):::core B --> |"事件完成<br/>(别人唤醒)"| R N --> |"正常 / 异常结束"| E(结束态):::edge B -.-> |"✗ 不存在"| N R -.-> |"✗ 不存在"| B classDef core fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a5f classDef edge fill:#f1f5f9,stroke:#94a3b8,color:#334155 linkStyle 6,7 stroke:#dc2626,stroke-width:2px,color:#dc2626
四条边分别是:
- 就绪 → 运行:被调度程序选中。这是进入运行态的唯一途径。
- 运行 → 就绪:时间片用完,或被更高优先级的进程抢占。进程本身没出问题,只是暂时让出 CPU。
- 运行 → 阻塞:进程主动执行阻塞原语。比如它调用
read()要读磁盘,数据还没到位,它自己主动把自己挂到阻塞队列上。 - 阻塞 → 就绪:等待的事件发生了,由别的执行流(中断处理程序、释放资源的那个进程)执行唤醒原语把它放回就绪队列。
主动与被动:方向性千万别记反
第 3 条和第 4 条有一个非常重要的不对称,这是理解后面所有陷阱题的钥匙:
运行→阻塞是进程自己主动做的;阻塞→就绪是别人被动帮它做的。
为什么必须是这样?因为一个进程只有在正在运行的时候才能执行指令,才能”主动”做任何事。而它一旦阻塞,就不再占有 CPU,它连一条指令都执行不了,当然不可能自己把自己唤醒。必须有外力。
这个不对称直接推出两条不存在的边。
两条不存在的边
阻塞 → 运行:不存在。
阻塞的进程被唤醒后,只能先回到就绪队列排队,等调度程序再次选中它才能上 CPU。它不能插队直达运行态。
道理很直白:唤醒它的那个执行流(比如中断处理程序)无权决定 CPU 接下来给谁——那是调度程序的职责。而且此刻可能有优先级更高的进程正在就绪队列里等着,凭什么让刚醒的这个先跑?
就绪 → 阻塞:不存在。
就绪态的进程没有在执行,它没有机会去请求任何资源,也就无从”得不到满足”而阻塞。要阻塞,必须先被调度上 CPU,在运行过程中发出请求,然后才可能因请求不满足而转入阻塞。
疑问点:就绪进程上了 CPU 却仍缺资源,会怎样
进程由就绪态转入运行态后,若仍缺少所需资源,是否会被重新挂入阻塞队列? 操作系统既然始终维护各类资源的可用数量,这种情形是否还会出现?
这个问题触及”为什么就绪→阻塞不存在”的实质,需分两层回答。
第一层:就绪态的定义已经排除了这种情况。 就绪的含义是”除 CPU 外一切资源都已到手”。如果它还缺别的资源,那它按定义就不该在就绪队列里,而应该在阻塞队列里。所以”就绪上了 CPU 却发现没资源”这个场景,在已经持有的资源上不会发生。
第二层:但它可以请求新资源。 进程上了 CPU 之后继续往下执行,完全可能发出一个新的请求(比如现在要打印了)。如果这个新资源拿不到,它就阻塞——但注意此时它的路径是 运行 → 阻塞,而不是”就绪 → 阻塞”。它是先跑起来了,再阻塞的。
至于 OS 是否始终维护资源数:是的,OS 通过信号量、资源分配表等结构记录每类资源的可用数量。但这不能消除阻塞,只是让”能不能拿到”这个判断有据可依。资源就那么多,拿不到就是拿不到。
挂起态:一个正交的维度
挂起态常被误当成”第六个状态”塞进上面那张图,这会让人很乱。正确的理解是:挂起是另一个维度。
- 原来的三态问的是:缺的是什么(缺 CPU / 缺事件 / 什么都不缺)
- 挂起问的是:是否驻留内存(在内存为活动;被换出到外存为静止)
两个维度正交,于是组合出四种:活动就绪、静止就绪、活动阻塞、静止阻塞。
引入挂起是为了在内存紧张时把一些进程整个换到外存去,腾出内存。这就和第 3 章的对换技术接上了。被挂起的进程即使等待的事件完成了,也只能变成”静止就绪”,必须先被换回内存才有资格竞争 CPU。
边界
”任何时刻都只有一个进程处于运行态”——这句话对吗
疑问点:"只有一个"与"有且仅有一个"哪个表述正确
命题:“在单处理机系统中,任何时刻都只有一个进程处于运行态。” 参考答案称该命题错误,理由是死锁时没有进程运行。 那么是否应将其改述为”有且仅有一个”?“只有一个”在中文里似乎已可涵盖 0 个或 1 个。
这道题的措辞确实值得较真,但上述修正方向是反的,需要彻底钉死。
准确的表述是:单处理机下运行态进程数至多为 1,即 0 个或 1 个。
- 正常情况下是 1 个。
- 但当所有进程都阻塞(比如全在等 I/O),或者发生死锁时,就绪队列为空,运行态进程数是 0。这时 CPU 跑的是闲逛进程,而闲逛进程通常不被算作用户进程。
所以:
- “任何时刻至多有一个” ✅ 正确
- “任何时刻有且仅有一个” ❌ 错误(漏了 0 的情况)
因此把命题改成”有且仅有一个”,反而是把对的改成错的。“只有一个”在中文里确实容易读成”恰好一个”,但在考试语境中它对应的是”至多一个”这一正确命题。处理此类措辞题的通用做法是先追问:0 个的情形是否存在? 在单处理机且全部阻塞或死锁的场景下,答案是存在。
就绪队列里最多能有几个
疑问点:调度的瞬间,就绪队列里到底有几个
- 在单处理机系统中,若同时存在 10 个进程,则处于就绪队列中的进程最多有()个?
疑问在于:既然进程是先被放入就绪队列、再被取出投入运行,那么在取出之前的那一瞬间,队列中不是应当有 10 个进程吗?
答案是 9。
这个追问抓住了一个真实的时序细节,但结论仍是 9。原因在于题目问的是”进程状态”,而不是”某一瞬间队列数据结构中的元素个数”。
一个进程一旦被调度程序选中,它的状态就已经变成运行态了。状态的改变是通过修改 PCB 里的状态字段完成的,这个修改和”把它从就绪队列摘下来”是同一个原语里的动作,对外不可分割。所以不存在一个合法的观测时刻,让某个进程同时既是运行态又留在就绪队列里。
换一个角度更清楚:10 个进程中必有 1 个在运行(否则 CPU 空转,题目默认系统正常工作),其余 9 个至多全部就绪。故就绪队列最多 9 个。
如果题目改成”最多有几个进程处于阻塞态”,答案就是 10——因为可以全部阻塞,此时无人运行,这正好又印证了上一条”运行态可以是 0 个”。
释放一台打印机,会改变谁的状态
疑问点:释放一台打印机,唤醒一个还是全部
- 一个进程释放了一台打印机,它可能改变()的状态。 A.自身进程 B.输入/输出进程 C.另一个等待打印机的进程 D.所有等待打印机的进程
疑问在于:为何不是 D——等待打印机的进程不应全部由阻塞态转为就绪态吗?
答案是 C,不是 D。
关键在于释放的是”一台”打印机。打印机是互斥共享的临界资源,一台只能给一个进程用。所以这一台被释放出来,只够唤醒一个等待者。
唤醒原语的语义是”从该资源的等待队列队首取出一个进程,把它变成就绪态”,剩下的进程继续阻塞着等下一次释放。
关联辨析:
notify与notifyAllJava 中
notify()唤醒一个等待线程,notifyAll()唤醒全部。notifyAll之所以在工程上常被推荐,恰恰是因为它不精确——它唤醒所有等待者重新竞争,依靠while循环重新检查条件来兜底,代价是惊群效应。而教材中”释放一台打印机”是资源语义:可用资源只有一份,唤醒多个没有意义,被唤醒者也拿不到资源。因此它对应
notify而非notifyAll。判据:本次释放出的资源够几个进程使用,就唤醒几个。
对照速查
| 转换 | 是否存在 | 触发者 | 主动/被动 |
|---|---|---|---|
| 就绪 → 运行 | ✅ | 调度程序 | — |
| 运行 → 就绪 | ✅ | 时间片到 / 被抢占 | 被动 |
| 运行 → 阻塞 | ✅ | 进程自己调用阻塞原语 | 主动 |
| 阻塞 → 就绪 | ✅ | 中断处理程序 / 其他进程唤醒 | 被动 |
| 阻塞 → 运行 | ❌ | — | 必须先回就绪排队 |
| 就绪 → 阻塞 | ❌ | — | 没上 CPU 就无从请求资源 |
| 问法 | 单处理机下的答案 |
|---|---|
| 运行态进程最多几个 | 1 |
| 运行态进程最少几个 | 0(全阻塞或死锁) |
| n 个进程时就绪队列最多几个 | n − 1 |
| n 个进程时阻塞态最多几个 | n |
考点
- 判断某条状态转换是否可能(阻塞→运行、就绪→阻塞是标准错误选项)
- “至多一个”与”有且仅有一个”的措辞辨析
- n 个进程时各状态的最多/最少个数
- 释放一份资源唤醒一个而非全部
- 挂起态与阻塞态是两个正交维度
链接
- 🏠 返回总览:操作系统第 2 章:进程与线程总览
- ⬅️ 上一节:2.1.2 进程的组成
- ➡️ 下一节:2.1.4 进程控制
- 📖 名词库:第 2 章名词库