一、进程的五状态模型与转换
进程在生命周期中会在不同状态之间切换,最经典的五状态模型完整描述了进程从创建到销毁的全过程。
1. 五种核心状态
- 新建态(New):进程正在被创建,OS 正在为其分配资源、初始化 PCB,尚未进入就绪队列。
- 就绪态(Ready):进程已经准备好运行,只差 CPU 资源,等待调度程序分配 CPU 时间。
- 运行态(Running):进程获得 CPU,正在执行指令。单 CPU 系统同一时刻只有一个进程处于运行态。
- 阻塞态(Blocked/Waiting):进程因等待某事件(如 I/O 完成、信号量、用户输入)而暂停运行,即使 CPU 空闲也无法执行。
- 终止态(Terminated):进程执行完毕或异常退出,OS 正在回收其资源、移除 PCB。
2. 状态转换条件与实例
表格
| 转换方向 | 触发条件 | 实例 |
|---|---|---|
| 新建 → 就绪 | 进程创建完成,资源分配完毕 | 双击打开软件,程序加载完成后进入就绪队列 |
| 就绪 → 运行 | 调度程序选中该进程,分配 CPU 使用权 | 时间片轮转调度中,轮到当前进程执行 |
| 运行 → 就绪 | 时间片用完;有更高优先级进程进入就绪态 | 进程执行 20ms 后时间片耗尽,回到就绪队列排队 |
| 运行 → 阻塞 | 进程请求 I/O;等待同步事件;主动休眠 | 进程调用scanf()等待用户键盘输入,进入阻塞 |
| 阻塞 → 就绪 | 等待的事件已经发生 | 用户输入完成,I/O 中断触发,进程被唤醒 |
| 运行 → 终止 | 程序正常结束;出现致命错误;被其他进程杀死 | 程序执行完 return 0;程序崩溃;任务管理器结束进程 |
关键注意点
阻塞态不能直接转换为运行态:阻塞的进程事件完成后,必须先回到就绪态,等待调度程序统一分配 CPU。
二、进程控制块(PCB)与上下文切换
PCB 是 OS 管理进程的核心数据结构,系统通过 PCB 感知进程的存在。进程创建时生成 PCB,进程销毁时回收 PCB。
PCB 核心内容
- 进程标识信息:进程 ID(PID)、父进程 ID、用户 ID,用于唯一标识进程。
- 处理机状态信息:通用寄存器、程序计数器(PC)、程序状态字(PSW)、栈指针等,用于上下文切换时保存 / 恢复现场。
- 进程调度信息:进程状态、优先级、调度参数、等待时间、已执行时间。
- 资源管理信息:内存地址空间、打开的文件列表、占用的 I/O 设备、信号量列表。
上下文切换的完整过程(实例)
当 CPU 从进程 A 切换到进程 B 时:
- CPU 硬件自动把当前程序计数器、寄存器等数据压入进程 A 的内核栈;
- OS 内核将这些硬件状态保存到进程 A 的 PCB 中;
- OS 把进程 A 的状态从 “运行态” 改为 “就绪态”,放回就绪队列;
- 从就绪队列中选中进程 B,读取进程 B 的 PCB 信息;
- 根据 PCB 中的数据,恢复进程 B 的寄存器、程序计数器等硬件状态;
- 进程 B 从上次暂停的位置继续执行,状态变为 “运行态”。
上下文切换存在固有开销:切换期间 CPU 不执行任何用户代码,因此频繁切换会降低系统整体效率。
三、进程调度算法与实例计算
进程调度的任务是从就绪队列中按照既定规则选中一个进程,将 CPU 分配给它。不同算法适用于不同业务场景。
1. 先来先服务(FCFS, First Come First Served)
- 规则:按进程到达就绪队列的先后顺序调度,先到的先执行。
- 特点:非抢占式,实现简单;对短作业不友好,平均等待时间长。
- 实例计算: 三个进程 P1、P2、P3,到达时间均为 0,服务时间分别为 24ms、3ms、3ms。 执行顺序:P1 → P2 → P3
- P1:等待时间 0,周转时间 24
- P2:等待时间 24,周转时间 27
- P3:等待时间 27,周转时间 30
- 平均周转时间:(24+27+30)/3 = 27ms
2. 短作业优先(SJF, Shortest Job First)
- 规则:优先调度服务时间最短的进程。
- 特点:平均等待时间最短,系统吞吐量高;对长作业不友好,可能出现 “饥饿” 现象。
- 实例计算(同上述进程): 执行顺序:P2 → P3 → P1
- P2:等待时间 0,周转时间 3
- P3:等待时间 3,周转时间 6
- P1:等待时间 6,周转时间 30
- 平均周转时间:(3+6+30)/3 = 13ms,性能远优于 FCFS。
3. 时间片轮转(RR, Round Robin)
- 规则:每个进程分配一个固定大小的时间片,时间片用完就强制让出 CPU,回到就绪队列末尾排队。
- 特点:抢占式,公平性好,响应速度快,适用于分时系统;时间片大小直接影响系统性能。
- 实例计算: 三个进程 P1、P2、P3,到达时间均为 0,服务时间分别为 5ms、3ms、4ms,时间片为 2ms。 执行序列:P1 (2) → P2 (2) → P3 (2) → P1 (2) → P2 (1) → P3 (2) → P1 (1)
- P1 周转时间:13ms
- P2 周转时间:9ms
- P3 周转时间:12ms
- 平均周转时间:(13+9+12)/3 ≈ 11.33ms
4. 优先级调度
- 规则:每个进程分配优先级,优先调度优先级高的进程。
- 分类:静态优先级(创建时确定,运行中不变)、动态优先级(运行中动态调整,防止长作业饥饿)。
- 实例:操作系统中,内核进程优先级高于用户进程;实时音视频进程优先级高于后台下载进程。
四、进程同步与互斥
当多个进程共享临界资源(同一时间只能被一个进程使用的资源,如打印机、共享变量)时,需要通过同步互斥机制保证数据一致性。
核心概念
- 互斥:多个进程不能同时进入临界区(访问临界资源的代码段)。
- 同步:多个进程需要按照约定的顺序执行,比如生产者生产完成后,消费者才能消费。
- 信号量(Semaphore):OS 提供的同步互斥工具,本质是一个整数变量,配合 P、V 两个原子操作使用。
- P 操作:信号量减 1,如果结果 < 0,进程阻塞;
- V 操作:信号量加 1,如果结果≤0,唤醒一个阻塞的进程。
经典实例:生产者 - 消费者问题
场景:有一个大小为 n 的缓冲区,生产者进程往缓冲区放数据,消费者进程从缓冲区取数据。
- 缓冲区满时,生产者不能放数据;
- 缓冲区空时,消费者不能取数据;
- 同一时间只能有一个进程操作缓冲区。
信号量设置:
empty = n:空缓冲区数量full = 0:满缓冲区数量mutex = 1:互斥信号量,保护缓冲区
伪代码实现:
c
运行
// 生产者进程 while(1) { 生产数据; P(empty); // 申请空缓冲区 P(mutex); // 进入临界区 把数据放入缓冲区; V(mutex); // 退出临界区 V(full); // 满缓冲区数量+1 } // 消费者进程 while(1) { P(full); // 申请满缓冲区 P(mutex); // 进入临界区 从缓冲区取出数据; V(mutex); // 退出临界区 V(empty); // 空缓冲区数量+1 消费数据; }关键说明:P 操作的顺序不能颠倒,必须先执行同步信号量的 P,再执行互斥信号量的 P,否则会触发死锁。
五、进程与线程的核心区别
随着并发需求提升,线程(Thread)被引入,作为CPU 调度的基本单位,进一步降低了并发的系统开销。
核心区别
表格
| 维度 | 进程 | 线程 |
|---|---|---|
| 资源拥有 | 资源分配的基本单位,拥有独立内存空间和系统资源 | 不拥有独立资源,共享所属进程的全部资源 |
| 调度单位 | 早期操作系统的调度单位 | 现代操作系统的 CPU 调度基本单位 |
| 切换开销 | 大,需要切换页表、刷新 TLB、保存完整 PCB | 小,只需保存线程上下文,共享进程内存 |
| 通信方式 | 需要通过专门的 IPC 机制,开销大 | 直接读写进程共享内存,通信便捷 |
| 稳定性 | 一个进程崩溃不影响其他进程 | 一个线程崩溃,整个进程都会崩溃 |
实例
Chrome 浏览器采用多进程架构:
- 每个标签页是一个独立的渲染进程,插件、网络、GPU 也各有独立进程;
- 单个标签页崩溃不会导致整个浏览器闪退,提升了整体稳定性;
- 每个进程内部又有多个线程,比如渲染主线程、IO 线程、合成线程,分别负责不同的子任务。