什么时候需要调度
- 当前进程主动放弃:正常终止、异常终止、主动请求 I/O 或资源而阻塞
- 当前进程被动放弃:时间片用完、更高优先级进程进入就绪队列、有更紧急的事要处理(如 I/O 中断)
这一页回答两个问题:多个程序怎么“同时”跑(进程、线程),CPU 只有一个时轮到谁用(调度)。进程是为了让并发可控而造出来的,线程是为了让切换更便宜,调度算法则是在“公平、快、不饿死”之间做取舍。
创建进程就是创建 PCB,撤销进程就是撤销 PCB。操作系统管进程,实际是在管一张张 PCB。
进程和进程实体的区别:进程实体是静态的“那堆东西”,进程是它的一次运行过程,是动态的。
typedef struct PCB { int pid, uid; // 描述信息:进程 ID、用户 ID int state; // 控制管理:当前状态 int priority; // 控制管理:优先级 int cpu_time; // 控制管理:已占用 CPU 时间 Context ctx; // 处理机相关:通用寄存器、PC、PSW、栈指针 void *code, *data; // 资源清单:程序段、数据段位置 void *stack; // 资源清单:栈 File *files[NOFILE]; // 资源清单:打开的文件、I/O 设备 struct PCB *next; // 链入就绪队列 / 某个阻塞队列 } PCB;
切换进程时,CPU 里的寄存器值被存进旧进程的 ctx,再从新进程的 ctx 恢复,这就是“保存现场 / 恢复现场”。
记状态转换不要背箭头,问一句:这时进程缺什么?只缺 CPU 就是就绪,缺别的就是阻塞。再问一句:这个动作谁能做?只有在 CPU 上执行的进程才能“主动”做事。
五个状态各缺什么
运行 正在 CPU 上执行
就绪 万事俱备,只缺 CPU
阻塞 在等 I/O、资源或消息,给它 CPU 也跑不了
创建 正在建 PCB、分资源,还没进就绪队列
终止 正在回收资源、撤销 PCB
原语是一段不可中断的程序(执行期间关中断)。状态转换要同时改 PCB 和移动队列,做到一半被打断,PCB 状态和它所在的队列就对不上了。
| 原语 | 对应转换 | 做了什么(按顺序) | 什么时候用 |
|---|---|---|---|
| 创建 | 无 → 创建 → 就绪 | 申请空白 PCB → 分配资源(内存不够就停在创建态)→ 初始化 PCB → 插入就绪队列 | 用户登录、作业调度、系统提供服务、应用请求(如 fork()) |
| 终止(撤销) | 运行 / 就绪 / 阻塞 → 终止 | 按 PID 找到 PCB → 若在运行,剥夺 CPU 给别人 → 终止其所有子孙进程 → 归还全部资源 → 删除 PCB | 正常结束、异常结束(越界、非法指令)、外界干预(用户杀进程) |
| 阻塞 block | 运行 → 阻塞 | 找到 PCB → 保护现场,状态改为阻塞 → 插入该事件的阻塞队列 | 请求资源失败、等 I/O 完成、等合作进程的数据。由进程自己调用 |
| 唤醒 wakeup | 阻塞 → 就绪 | 在阻塞队列中找到 PCB → 移出,状态改为就绪 → 插入就绪队列 | 等的事件发生了。由别的进程或中断处理程序调用 |
| 切换 | 运行 ↔ 就绪 等 | 把 CPU 现场存入旧 PCB → 改旧 PCB 状态并移入相应队列 → 选新进程,改其 PCB 状态 → 更新内存管理数据结构 → 恢复新进程现场 | 时间片到、有更高优先级进程到达、当前进程阻塞或终止 |
block 和 wakeup 必须成对出现:进程因为某事件阻塞,就得有别的进程在事件发生时唤醒它,否则它永远睡下去。
OS 在内存里划一块两个进程都能访问的空间,进程直接读写。
以格式化消息(消息头 + 消息体)为单位,用 OS 提供的 send / receive 原语收发。
连接读写进程的一个特殊共享文件(pipe 文件),本质是内存里一块固定大小的缓冲区。
线程是一个轻量级的执行单元:自己只有很少的私有部分(够它在 CPU 上跑就行),其余资源都向所在进程借用。ULT 和 KLT 的全部区别,都来自一个问题:内核知不知道这个线程存在。
| 对比项 | 用户级线程 ULT | 内核级线程 KLT |
|---|---|---|
| 谁管理 | 应用程序里的线程库,内核看不到线程,只看到进程 | 操作系统内核,内核为每个线程建 TCB |
| 线程切换要不要进内核 | 不需要 在用户态完成,开销小 | 需要 要从用户态切到核心态,开销较大 |
| 一个线程阻塞 | 整个进程阻塞 内核以为是进程在等 I/O | 只阻塞它自己 同进程其他线程照常运行 |
| 能否多核并行 | 不能 内核把 CPU 分给进程,一个进程同一时刻只在一个核上 | 能 多个线程可分到多个核 |
| 调度单位 / CPU 分配单位 | 进程 | 内核级线程 |
多个 ULT 映射到 1 个 KLT。
每个 ULT 对应一个 KLT。
n 个 ULT 映射到 m 个 KLT(n ≥ m)。
| 归属 | 内容 | 为什么 |
|---|---|---|
| 共享(属于进程) | 地址空间:代码段、数据段(全局变量、静态变量)、堆;打开的文件;I/O 设备等其他资源 | 资源分配的单位是进程,线程只是借用 |
| 私有(属于线程) | 线程 ID 和 TCB、寄存器(含 PC)、栈(局部变量、函数调用链)、线程状态 | 每个线程要独立执行,执行到哪里(PC)、用到哪些临时值(寄存器、栈)必须各有一份 |
| 层级 | 做什么 | 在哪之间 | 频率 | 引起的状态变化 |
|---|---|---|---|---|
| 高级调度(作业调度) | 从外存后备队列选作业调入内存,建立进程。每个作业只调入一次、调出一次 | 外存 → 内存 | 最低 | 无 → 创建 → 就绪 |
| 中级调度(内存调度) | 把暂时不能运行的进程调到外存等待(挂起),内存宽裕时再调回(激活)。目的是提高内存利用率和系统吞吐量 | 内存 ↔ 外存(对换区) | 中等 | 挂起态 ↔ 就绪 / 阻塞 |
| 低级调度(进程调度) | 从就绪队列选一个进程,把 CPU 分给它。最基本、不可缺少 | 内存 → CPU | 最高(几十毫秒一次) | 就绪 → 运行 |
这些情况下即使出现了调度条件,也要置请求调度标志,等过程结束后再调度。
能。进程在访问打印机这类普通临界资源时,访问可能很慢。如果不让调度,CPU 会一直空等打印机。只有内核程序的临界区才不能被打断,因为它通常很短、访问的是内核数据结构,而且直接影响 OS 管理工作。
调度算法改变不了一个作业的运行时间和 I/O 时间,只能改变它在就绪队列里等多久。所以比较算法时,看等待时间最直接。
选择题最常考:哪个会饥饿、哪个是抢占式、哪个对长作业/短作业有利、哪个适合分时。表里每一格都能从“排队依据”推出来。
| 算法 | 排队依据 | 抢占性 | 会饥饿吗 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|---|---|
| FCFS 先来先服务 | 到达先后 | 非抢占 | 不会 | 公平、实现简单 | 对短作业不利(排在长作业后面);对 I/O 繁忙型不利 | 作业调度、进程调度都可;有利于长作业、CPU 繁忙型 |
| SJF / SPF 短作业(进程)优先 | 要求运行时间最短 | 非抢占 | 会 | 平均等待时间、平均周转时间最短(所有作业同时到达时最优) | 对长作业不利;运行时间是用户估计的;不考虑紧迫程度 | 作业调度、进程调度 |
| SRTF 最短剩余时间优先 | 剩余运行时间最短 | 抢占(新进程到达时比较) | 会 | 平均等待、平均周转比 SJF 更短 | 同 SJF,且切换更频繁 | 进程调度 |
| 优先级调度 | 优先级(本页约定:数字小 = 优先级高) | 抢占、非抢占均可 | 会 | 能区分紧急程度,适合实时系统 | 静态优先级下,低优先级进程可能一直等 | 作业调度、进程调度 |
| HRRN 高响应比优先 | 响应比 = (等待 + 要求服务) ÷ 要求服务,取最大 | 非抢占 | 不会 | 兼顾长短作业:等待相同时短的先上;服务时间相同时等得久的先上;长作业等久了响应比会升高 | 每次调度都要算一遍所有进程的响应比 | 作业调度、进程调度 |
| RR 时间片轮转 | 到达顺序 + 时间片 | 抢占(时间片到时由时钟中断剥夺) | 不会 | 公平、响应快 | 切换有开销;不区分紧急程度 | 分时系统;只用于进程调度 |
| 多级队列 | 按进程类型固定分到不同队列,队列间有固定优先级 | 队列间通常抢占 | 会 | 不同类型进程用不同策略(如前台 RR、后台 FCFS) | 进程不能换队列,不灵活 | 进程调度 |
| 多级反馈队列 | 队列优先级 + 各队列内 FCFS / RR,进程可降级 | 抢占(高级队列来新进程时) | 会 | 兼顾公平、短作业优先、响应快,不必估计运行时间 | 新进程不断到来时,低级队列饥饿 | 通用,进程调度 |
默认数据是王道教材的经典例题(P1 0/7、P2 2/4、P3 4/1、P4 5/4),可以对着书核对。进程表可以直接改,改完立刻重算。单步看每一段为什么轮到它。
| 进程 | 到达时间 | 运行时间 | 优先数 数字小 = 优先级高 |
|---|
| 情境 | 能否进行进程调度 / 切换 | 一句话原因 |
|---|---|---|
| 正在处理中断 | 不能 | 中断处理不属于任何进程,也不能被打断 |
| 进程在内核程序临界区中 | 不能 | 内核数据(如就绪队列)改到一半,切走会乱 |
| 原子操作 / 原语执行中 | 不能 | 原语要求不可分割,执行时关中断 |
| 进程在普通临界区(如使用打印机) | 能 | 不让调度 CPU 就会陪着慢速设备空等 |
| 进程请求 I/O 而阻塞 | 能(必须) | 主动放弃 CPU |
| 时间片用完 / 更高优先级进程到达(抢占式) | 能 | 被动放弃 CPU |
| 系统调用进入核心态 | 不一定 | 模式切换(用户态 → 核心态)不等于进程切换 |