2. 进程状态模型:三态模型、五态模型与状态转换图
进程从创建到终止,会经历多个不同的生命周期阶段。操作系统通过状态模型来管理这些阶段,确保 CPU 资源被合理分配。理解进程状态模型是掌握进程调度、同步与通信的基础。
2.1 三态模型:进程核心生命周期
三态模型是进程管理中最基础、最核心的状态模型,包含三个基本状态:就绪(Ready)、运行(Running) 和 阻塞(Blocked)。几乎所有现代操作系统都以此为基础进行扩展。
核心概念: 在任何时刻,一个 CPU 核心上最多只有一个进程处于运行状态;就绪队列中存放所有可被调度执行的进程;阻塞队列中存放等待 I/O 或资源的进程。
三态定义与转换条件
| 状态 | 定义 | 典型触发事件 |
|---|---|---|
| 就绪(Ready) | 进程已获得除 CPU 之外的所有资源,等待调度器分配 CPU | 进程创建完成、时间片用完被剥夺 CPU、阻塞事件完成 |
| 运行(Running) | 进程已获得 CPU,正在执行指令 | 调度器从就绪队列选中该进程 |
| 阻塞(Blocked) | 进程因等待某事件(如 I/O 完成、信号量)而暂停执行 | 发起系统调用(如 read()、sleep())、等待锁资源 |
三态转换关系
- 就绪 → 运行: 调度器(Dispatcher)从就绪队列中选择一个进程,分配 CPU。
- 运行 → 就绪: 时间片耗尽、被更高优先级进程抢占、主动让出 CPU(如 sched_yield())。
- 运行 → 阻塞: 进程主动发起 I/O 请求、等待锁、调用 sleep() 等阻塞式系统调用。
- 阻塞 → 就绪: 等待的事件完成(如磁盘数据到达、锁被释放、定时器超时)。
实战要点: 在 Linux 中,
ps -l 命令的 S 列可以查看进程状态:R(Running/Runnable)、S(Sleeping,即阻塞)、D(不可中断睡眠)。注意 Linux 的 R 状态实际对应三态模型中的“就绪 + 运行”合并态。
2.2 五态模型:更精细的进程管理
三态模型无法描述进程的创建和终止阶段,也无法处理进程被挂起的场景。五态模型在三态基础上增加了两个状态,并引入了挂起(Suspend)概念,使操作系统能更灵活地管理内存和 CPU 资源。
五态定义
| 状态 | 含义 | 典型场景 |
|---|---|---|
| 新建(New) | 进程正在被创建,尚未进入就绪队列 | fork() 调用后、资源分配完成前 |
| 就绪(Ready) | 进程已就绪,等待调度 | 同三态模型 |
| 运行(Running) | 进程正在 CPU 上执行 | 同三态模型 |
| 阻塞(Blocked) | 进程等待某事件 | 同三态模型 |
| 终止(Terminated) | 进程执行完毕,等待父进程回收资源(僵尸态) | exit() 调用后、wait() 回收前 |
挂起状态:内存换出
当系统内存不足时,操作系统可以将进程的整个地址空间换出(swap out)到磁盘交换区,此时进程处于挂起就绪(Ready Suspend)或挂起阻塞(Blocked Suspend)状态。挂起状态不是五态模型的标配,但许多教材和实际系统(如早期 UNIX)会将其纳入,形成七态模型。
常见误区: 挂起(Suspend)与阻塞(Block)不同。阻塞是进程主动等待事件;挂起是操作系统被动将进程换出内存,进程本身并不知道自己被挂起。挂起状态的进程不参与 CPU 调度,直到被换回内存。
五态模型状态转换
- 新建 → 就绪: 进程创建完成,资源分配完毕,进入就绪队列。
- 运行 → 终止: 进程执行 exit() 或收到致命信号(如 SIGKILL)。
- 就绪 → 新建(罕见): 创建过程中资源不足,回退到新建状态(部分系统支持)。
- 阻塞 → 终止: 阻塞中的进程被强制终止(如 kill -9)。
2.3 状态模型在 Linux 中的体现
Linux 内核实际使用的进程状态比理论模型更丰富,但核心逻辑与三态/五态模型一致。通过 ps 和 top 命令可以直观观察进程状态。
Linux 进程状态与理论模型对照
| Linux 状态码 | Linux 状态名 | 对应理论模型 | 说明 |
|---|---|---|---|
| R | TASK_RUNNING | 就绪 + 运行 | 进程正在运行或可被调度 |
| S | TASK_INTERRUPTIBLE | 阻塞(可中断) | 等待事件,可被信号唤醒 |
| D | TASK_UNINTERRUPTIBLE | 阻塞(不可中断) | 等待 I/O,不可被信号打断 |
| T | TASK_STOPPED / TRACED | 暂停 / 跟踪 | 收到 SIGSTOP 或被调试器暂停 |
| Z | EXIT_ZOMBIE | 终止(僵尸) | 进程已退出,但 PCB 未被父进程回收 |
| X | EXIT_DEAD | 终止(已回收) | 进程完全消失,通常不可见 |
实战命令:
ps -eo pid,stat,comm,wchan — 查看进程状态和等待原因top -p 1234 — 实时监控指定进程的状态变化cat /proc/1234/status — 查看进程的详细状态信息
2.4 状态转换的触发机制
进程状态转换由系统调用、硬件中断和调度器决策共同驱动。以下列出关键转换的触发源:
| 转换路径 | 触发源 | 典型系统调用 / 事件 |
|---|---|---|
| 就绪 → 运行 | 调度器 | schedule()、上下文切换 |
| 运行 → 就绪 | 时钟中断 / 调度器 | 时间片到期、sched_yield()、优先级抢占 |
| 运行 → 阻塞 | 进程主动调用 | read()、write()、sleep()、wait()、lock() |
| 阻塞 → 就绪 | 中断 / 内核事件 | 磁盘中断、网络数据到达、定时器超时、信号唤醒 |
| 运行 → 终止 | 进程主动 / 信号 | exit()、SIGKILL、SIGTERM |
调试技巧: 使用
strace -p <pid> 可以跟踪进程的系统调用,观察进程何时进入阻塞状态(如 read 系统调用阻塞等待输入)。结合 /proc/<pid>/wchan 可以查看进程在内核中等待的具体函数。
2.5 状态模型与 fork 的关联
在后续章节的 fork 实战中,状态模型将直接指导以下行为:
- fork() 调用后: 子进程进入就绪状态,等待调度器分配 CPU。父子进程谁先运行取决于调度策略。
- wait() 调用: 父进程调用 wait() 后进入阻塞状态,直到子进程终止。
- 僵尸进程: 子进程先 exit() 而父进程未 wait(),子进程进入终止(僵尸)状态,PCB 残留。
- IPC 通信阻塞: 管道读端无数据、消息队列空、共享内存加锁等场景,都会使进程进入阻塞状态。
常见陷阱: 初学者常误以为 fork() 后子进程立即运行。实际上子进程只是进入就绪队列,具体运行时机由调度器决定。在多核系统上,父子进程可能同时运行;在单核系统上,它们交替执行。不要对执行顺序做任何假设,必须使用同步机制(如 wait、信号量)来保证确定性。