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 状态实际对应三态模型中的“就绪 + 运行”合并态。
三态模型状态转换图 就绪(Ready) 等待 CPU 调度 运行(Running) 正在执行指令 阻塞(Blocked) 等待事件完成 调度选中 时间片耗尽 / 抢占 I/O 请求 / 等待锁 事件完成(I/O 结束 / 锁释放) 说明:阻塞态不能直接回到运行态,必须经过就绪态等待重新调度

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)。
五态模型状态转换图 新建(New) fork() 创建中 就绪(Ready) 等待 CPU 运行(Running) 执行指令 阻塞(Blocked) 等待事件 终止(Terminated) 等待回收 创建完成 调度选中 时间片耗尽 / 抢占 I/O 请求 事件完成 exit() / 信号 说明:五态模型增加了新建和终止状态,更完整地描述了进程的完整生命周期

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、信号量)来保证确定性。