操作系统考前突击¶
Danger
这门课距离考试还有一坤天, 是时候开学了!
听别人说内容来自: 王道考研 + 最后一节课的超长超大PPT + 随机闪现模拟题
Chapter 1: 引论¶
(1) 操作系统的定义与作用:
- 定义: 是一组控制和管理计算机系统中的各种软硬件资源, 合理地组织计算机系统的工作流程, 方便用户使用的程序的集合
- 作用:
- 资源管理的观点: OS 是系统资源的管理者 (硬件和软件资源)
- 用户服务的观点: OS 是用户与裸机之间的接口
(2) 操作系统的四大特征:
- 并发
- def: 指两个或多个事件在同一时间间隔内发生
- 注意: 与并行 (Parallel) 的区别. 并行是指在同一时刻发生
- 共享
- def: 系统中的资源, 可供内存中 "多个并发执行的进程" 共同使用
- 虚拟
- def: 把一个物理实体映射为多个逻辑实体
- eg: 通过"时分复用"把一个CPU虚拟成多个CPU, 通过"空分复用"把 物理内存 虚拟成更大的 逻辑内存
- 异步
- def: 在"多道程序环境"下, 程序的执行进度是不确定的. "走走停停", 执行结果也可能不确定
(3) 操作系统的五大功能:
- 处理机管理
- 存储器管理
- 文件管理
- 设备管理
- 用户接口
(4) 操作系统的分类:
- 批处理操作系统
- 细分: 单道/多道
- 优点: 资源利用率高, 吞吐量大
- 缺点: 无交互能力, 周转时间长
- 分时操作系统
- 定义: 把CPU时间分成时间片, 轮流分配给各作业
- 特征: 交互性强, 独占性
- 实时操作系统
- 特征: 及时性, 可靠性
- 通用操作系统
- def: 如果一个 OS 兼有 批处理, 分时处理, 实时处理 三者中的两者及以上, 这样的 OS 称为 通用操作系统
核心概念
多道程序设计:
- 定义: 内存中 同时存放多道作业, 使它们处于开始和结束之间
- 目的: 利用 CPU 和 I/O 设备 的并行工作能力来提高系统效率
- 特点: 宏观上并行, 微观上串行
接口:
- 作业级接口: 命令行, GUI, 批处理 (用户直接用的)
- 程序级接口: System Call (程序员用的, 是程序请求OS服务的唯一途径)
Chapter 2: 处理机管理¶
Part 1: 进程与线程 - 基础概念¶
-
进程 (Process):
- 定义: 进程是程序的一次执行过程, 是系统进行资源分配和调度的一个基本单位
- 特征: 动态性(有生命周期), 并发性, 独立性, 异步性, 结构性
- 组成 (PCB)
- PCB (进程控制块, Process Control Block) 是进程存在的唯一标志, 常驻内存
- 包含: 进程ID, CPU现场(寄存器值), 调度信息(优先级/状态), 控制信息(资源清单)
- 进程的三种基本状态
- 运行 (Running): 占用 CPU 执行
- 就绪 (Ready): 万事俱备, 只欠 CPU(资源都齐了, 就等调度)
- 等待 (Waiting): 等待某个事件(如 I/O 完成, 信号量)发生

-
线程 (Thread):
- 定义: 线程是进程内的一个可调度实体, 是处理机(CPU)调度的基本单位
- 注意区分: 进程是资源分配单位, 线程是CPU调度单位
- 引入目的: 减少并发执行时的时空开销(创建, 切换快), 提高并发性
-
进程 vs 线程的区别:
- 调度: 线程是调度单位, 进程是资源分配单位
- 并发性: 进程间可并发, 线程间也可并发
- 拥有资源: 进程拥有资源, 线程只拥有必不可少的资源(如寄存器, 栈), 但共享进程资源
- 开销: 线程切换开销远小于进程(不需要切页表等环境)
Part 2: 进程同步与互斥 - 信号量机制¶
(1) 什么是 PV 操作?
- 信号量 (S): 就是一个 "计数器", 代表资源的数量
- P 操作 (Wait): "申请/拿走"
- 意思就是: 我要用一个资源
- 动作: 把计数器 S 减 1 (\(S=S-1\))
- 关键规则: 如果减完发现 \(S<0\) (也就是本来就没资源了), 那你就在门口排队等着(阻塞), 别进去捣乱
- V 操作 (Signal): "释放/归还"
- 意思就是: 我用完了, 还回去
- 动作: 把计数器 S 加 1 (\(S=S+1\))
- 关键规则: 如果加完发现还有人在排队 (\(S \leq 0\)), 你就叫醒(唤醒)排队的那个人: "喂, 有空位了, 快进去!"
TLDR: P 是 减(拿钥匙), 减到负数就去睡; V 是 加(还钥匙), 加完喊人起来嗨
P(S):S--V(S):S++
(2) 两大核心场景
- 互斥 (Mutex)
- 问题: 只有一个厕所(临界资源), 一次只能进一个人. 如果A正在用, B就不能进
- 解决方法:
- 设置一个信号量 mutex = 1(代表只有 1 把钥匙)
- 进去前 (P): 拿走钥匙 (
P(mutex)). 如果没有钥匙了, 就在门口等 - 出来后 (V): 还回钥匙 (
V(mutex)). 让下一个人拿
- 代码模式:
C 1 2 3
P(mutex); // 拿锁 [上厕所...] // 临界区代码 V(mutex); // 开锁
- 同步 (Sync)
- 问题: A 跑完第一棒, B 才能跑第二棒. B 必须等 A 的信号
- 解决方法:
- 设置一个信号量 S = 0(代表 A 还没跑完, B 手里没棒子)
- B 的代码: P(S). 因为 S=0, 一减就是负数, B 只能死等
- A 的代码: 跑完了, 执行 V(S). 把 S 变成 1, 相当于把棒子递给 B, 唤醒 B 接着
(3) 两类核心问题
[1] Producer-Consumer Problem:

[2] Readers-Writers Problem:

Part 3: CPU 调度 - 算法计算题¶
基础概念¶
(1) 三级调度
- 高级调度
- aka. 作业调度
- 地点: 磁盘 -> 内存
- 频率: 最低
- 低级调度
- aka. 进程调度
- 地点: 内存 -> CPU
- 频率: 最高
- 中级调度
- aka. 交换调度
- 地点: 内存 <-> 磁盘
- 频率: 中等 [目的是 节省内存空间]
(2) 调度方式: 抢占式与非抢占式
别人正在吃饭(用CPU)时, 能不能把他赶下来?
抢占定义
指的是: "新来的 process" 能不能抢走 "正在运行的 process" 的 CPU
- Non-preemptive [非抢占式]
- 不能赶走正在运行的进程
- 只要我抢到了 CPU, 我就一直用, 直到我用完(任务完成)或者我自己主动放弃(比如我去等 I/O 了)
- Preemptive [抢占式]
- 可以赶走正在运行的进程
- 虽然我正在用 CPU, 但如果更有资格的人来了(优先级更高), 或者我的时间到了(时间片用完), 操作系统会强行把 CPU 收回, 分配给别人
- 现代OS主要采用的方式!
(3) 调度时机
- 任务完成 or 出现异常
- 因某种原因由"运行"变成"等待"状态
- 时间片用完
- 采用"可抢占调度方式"时, 有更高优先级进程进入就绪队列
排队算法¶
(1) 三个核心概念
- 周转时间
- def: 从你提交作业开始, 到你做完作业离开为止, 一共花了多少时间
- 周转时间 = 运行时间 + 等待时间
- 等待时间
- def: 在就绪队列里纯排队的时间
- 等待时间 = 周转时间 - 运行时间
- 带权周转时间
- def: 带权周转时间 = (周转时间 / 运行时间)
(2) 三种核心算法 (怎么排队)
- FCFS: 等价于 FIFO
- 规则: 谁先到谁先用 CPU
- 特点: 简单, 但对短作业极不公平
- SJF: 短作业优先, 先用 CPU 的是短作业
- 规则: 贪心算法. 谁干活快(运行时间短), 谁先上
- 特点: 平均等待时间最短
- 注意: 分为"非抢占式"(一旦开始就不停)和"抢占式"(如果有更短的来了, 就把正在跑的挤下去, 也叫 SRTF). 默认情况通常指非抢占式
- Round-Robin, RR: 时间片轮询
- 规则: 雨露均沾. 设定一个时间片 (Time Quantum)(比如 q=4). 每个进程只能跑这么久, 跑不完就滚回队尾重新排队
Part 4: Deadlock - 银行家算法¶
(1) 死锁的基础概念
- 定义: 多个进程因竞争资源而造成的一种僵局(互相等待), 若无外力作用, 它们都将无法推进
- 原因:
- 系统资源不足
- 进程推进顺序不合适
(2) 死锁的四个必要条件
这四个条件必须同时具备才会死锁! 只要破坏其中一个, 死锁就解除了
- 互斥使用
- 资源是独占的, 我用你就不准用(如打印机)
- 不可剥夺
- 我抢到的资源, 除非我自愿放弃, 否则你不能强行抢走
- 请求保持
- 吃着碗里的, 看着锅里的. 我手里拿着资源(保持), 同时还要申请新资源(请求), 申请不到我就带着手里的资源一起等, 不肯放手
- 环路等待
- 存在一个进程链: A 等 B, B 等 C ... C 等 A, 形成一个闭环
(3) 死锁的处理方式
-
预防死锁
- 策略: 严格限制, 破坏四个必要条件之一
- 特点: 即能够保证死锁绝对不会发生, 但系统效率低(管得太宽了)
-
避免死锁 (Avoidance)
- 策略: 银行家算法
- 特点: 允许进程动态申请, 但要防止系统进入不安全状态
-
检测与解除 (Detection & Recovery)
- 策略: 不管它, 让死锁发生. 系统定时检测, 一旦发现死锁, 就通过撤销进程或剥夺资源来解除
预防死锁: 破坏条件
互斥条件通常改不了(硬件属性), 主要破坏后面三个:
- 破坏"请求保持"条件:
- 方法: 资源的静态分配法
- 解释: 进程运行前, 必须一次性申请全部资源. 要么全给, 要么一个都不给
- 破坏"环路等待"条件:
- 方法: 资源的顺序分配法
- 解释: 给系统所有资源编号. 规定进程只能按编号递增的顺序申请资源. (只能先拿1号, 再拿2号, 不能拿了2号回头要1号)
- 破坏"不可剥夺"条件:
- 方法: 当一个进程申请新资源得不到满足时, 必须先放弃已占有的资源(被动释放)
Chapter 3: 存储器管理¶
Part 1: 基础概念与连续分配¶
-
地址重定位:
- 逻辑地址: 程序里写的地址(从0开始)
- 物理地址: 内存条上真正的地址
- 映射方式:
- 静态重定位: 装入内存时一次性修改好, 运行期间不能动
- 动态重定位: 运行时才转换. 需要硬件支持(重定位寄存器). 现代OS都用这个
-
动态分区分配算法: 当一个新的作业来了, 要在内存的一堆空闲洞(分区)里找一个塞进去
- 首次适应 (First Fit): 从头开始找, 第一个能放下的就给它
- 特点: 空闲分区按地址递增排列
- 最佳适应 (Best Fit): 找大小最合适(最小能放下)的
- 特点: 空闲分区按容量递增排列
- 最坏适应 (Worst Fit): 找最大的那个切一块给它
- 特点: 空闲分区按容量递减排列
- 首次适应 (First Fit): 从头开始找, 第一个能放下的就给它
-
紧凑技术:
- 问题: 普通的可变分区分配会产生大量的外碎片(内存里有很多小空洞, 总和够大, 但分散着没法用)
- 解决方式: 紧凑. 把所有作业挪到一起, 把小洞拼成大洞
- 需要"动态重定位"支持
- 硬件支持: 重定位寄存器
- 因为作业在内存里被挪动了, 它的物理地址变了
- 必须采用动态重定位, 在程序执行时, 通过硬件自动把逻辑地址加上重定位寄存器中的基址, 算出新的物理地址
内/外碎片
- 内碎片
- 定义: 分配给作业的内存区域内部, 没被利用的空间
- 出现场景: 固定分区, 分页存储(最后一页通常填不满)
- 外碎片
- 定义: 内存中作业与作业之间, 太小而无法分配给新作业的空闲区
- 出现场景: 动态分区分配(可变分区)
- 解决: 紧凑技术
Part 2: 分页存储管理¶
"页"(Paging) + "块"(Block) + "地址变换"(PageTable + TLB)
-
基本原理:
- 切分:
- 进程逻辑空间切成 "页" (Page)
- 内存物理空间切成 "块"/"页框" (Block/Frame)
- 页大小 = 块大小
- 页表 (Page Table): 记录
页号 -> 块号的映射表 - 快表 (TLB): 为了快, 把常用的页表项放在高速缓存里. 命中率是计算"有效访问时间"的关键
- 切分:
-
地址变换:
- 逻辑地址结构: 前一部分是
页号 P, 后一部分是页内偏移量 W - 物理地址结构: 前一部分是
块号 B, 后一部分是块内偏移量 W - 转换步骤:
- 根据逻辑地址算出 P 和 W:
- P = 逻辑地址 / 页大小
- W = 逻辑地址 % 页大小
- 根据"页表"算出 B:
- B = 页表[P]
- 注意: 越界检查! 如果 P >= 页表长度, 报错
- 合并物理地址:
- 物理地址 = B * 块大小 + W
- 根据逻辑地址算出 P 和 W:
- 逻辑地址结构: 前一部分是
-
页面置换算法: 当内存满了, 要调入新页, 必须踢走一个旧页. 踢谁?
- 最佳置换 (OPT): 踢走以后最长时间不再访问的页
- 理论算法, 不可实现, 用于评估
- 先进先出 (FIFO): 踢走最早进来的页
- 最近最久未使用 (LRU): 踢走过去最长时间没用过的页
- 性能最好(最接近OPT), 但需要硬件支持(开销大)
- Clock 算法 (NRU): 像时钟一样转圈扫描. 检查"访问位", 如果是1就清0给次机会, 如果是0就踢走
- 最佳置换 (OPT): 踢走以后最长时间不再访问的页
-
对换技术: Swapping
- 目的: 内存不够时, 把暂时不运行的进程或页面调到外存(磁盘)的对换区, 腾出内存空间
- 类型:
- 整体对换: 以"整个进程"为单位(中级调度)
- 页面对换: 以"页"为单位(部分对换)
-
抖动/颠簸: Thrashing
- 现象: 频繁的缺页, 刚换进来又被换出去. CPU 忙着换页, 没空干活
- 原因: 给进程分配的物理块太少, 不够它主要工作集用的
- 解决:
- 给进程提供足够的物理块
- 采用局部置换: 若一个进程开始颠簸, 采用局部置换, 不会使其它进程也发生颠簸
- 控制缺页频率
Part 3: 虚拟内存¶
-
理论基础
- 局部性原理 (Locality): 程序在执行时, 访问的指令和数据倾向于聚簇(时间局部性, 空间局部性). 这是虚拟内存能跑通的根本原因
- 请求分页 (Demand Paging): 不一次性全装入, 用到哪一页再调入哪一页
-
虚拟存储器必须建立在离散分配的基础上
- 请求分页
- 请求分段
-
硬件支持
- 一定容量的内存
- 较大容量的外存
- "缺页(段)中断"机制
- "地址变换"机制
整理一下workflow. 非常重要!
我们可以把这个过程看作 CPU 拿着一个逻辑地址去"寻宝"的过程.
第一阶段: 快速查找¶
一切始于 CPU 生成一个 逻辑地址 (Logical Address). 系统自动将这个地址拆分为 页号 (Page Number, P) 和 页内偏移量 (Offset, W)
Step 1: 查 TLB (快表)
CPU 首先拿着页号 P 去查 TLB (Translation Lookaside Buffer)
-
情形 A: TLB 命中 (Hit)
- 运气很好! TLB 直接给出了对应的 物理块号 (Frame Number, F)
- 结果: 直接拼接物理地址 (F * 页大小 + W), 访问 Memory (内存). 流程结束(极速模式)
-
情形 B: TLB 未命中 (Miss)
- TLB 里没记录, 必须去查慢速的 Page Table (页表). 这通常需要多访问一次内存
第二阶段: Page Table Lookup¶
如果 TLB 没找到, CPU 就去访问内存中的 页表
Step 2: 查页表
根据页号, 找到对应的页表项. 此时要检查页表项中的 "状态位/存在位" (Valid/Present Bit)
-
情形 A: 页在内存中 (Valid)
- 页表项显示该页已经在内存里了(有对应的块号).
- 动作:
- 读出物理块号.
- 更新 TLB(把这条记录写进快表, 下次就快了).
- 生成物理地址, 访问 Memory. 流程结束.
-
情形 B: 页不在内存中 (Invalid) —— 触发缺页中断
- 页表项显示该页在磁盘上(状态位为0)
- 结果: 硬件产生一个 缺页中断 (Page Fault), 当前进程暂停, OS (操作系统) 接管控制权
第三阶段: 缺页处理¶
这是最耗时的阶段, 涉及 Disk (磁盘) I/O 和 Page Replacement (页面置换)
Step 3: 操作系统介入
- 保留现场: OS 保存当前进程的 CPU 寄存器状态
- 分析原因: 确认是合法的缺页(数据在磁盘), 而不是非法访问(越界)
Step 4: 磁盘寻址
OS 根据页表中的外存地址, 在 Disk (磁盘/对换区) 中找到那个丢失的页
Step 5: 内存分配 (关键分支)
OS 试图把这一页从磁盘搬到内存. 它检查 Memory 是否还有空闲的物理块?
- 分支 A: 有空闲块
- 直接分配一个空块
- 分支 B: 内存已满 (无空闲块)
- 执行页面置换算法 (如 LRU):
- 选出一个倒霉的"牺牲页" (Victim Page)
- 写回磁盘 (Swap Out): 如果该页被修改过(Dirty Bit = 1), 必须把它写回 Disk
- 更新页表: 把牺牲页的状态改为"无效/不在内存"
- 执行页面置换算法 (如 LRU):
Step 6: 数据调入 (Swap In)
- 启动 I/O 操作, 把目标页从 Disk 读入刚才腾出来的 Memory 物理块中
第四阶段: 恢复执行¶
Step 7: 更新映射
- OS 修改页表: 把目标页的状态改为"有效/在内存", 填入新的物理块号
- (可选) 更新 TLB
Step 8: 重启指令
- OS 恢复进程的上下文, 重新执行刚才那条导致缺页的指令
- 这次执行时: TLB 或 页表 必定命中, 数据顺利拿到
总结: 四个组件的互动关系¶
- TLB (快表): 是 页表 的缓存(Cache). CPU 先问它, 它不知道再问页表
- Page Table (页表): 是 Memory 的地图. 它告诉 CPU 数据是在 Memory 里, 还是被踢到了 Disk 上
- Disk (磁盘): 是 Memory 的后备仓库(虚拟内存的实体). 当 Memory 满时, 用它来暂存数据(Swapping)
- Memory (内存): 是 CPU 唯一能直接读写数据的地方. 所有在 Disk 上的数据, 必须先搬到这里才能用
Chapter 4: 设备管理¶
(1) I/O 控制方式
原则: 尽量减少主机(CPU)对 I/O 控制的干预, 把主机从繁杂的 I/O 事务中解脱出来
- 程序 I/O (轮询):
- 特点: CPU 必须不停地去问设备"好了没? 好了没?"
- 缺点: CPU 利用率极低, 串行工作
- 中断驱动:
- 特点: 设备准备好了就发一个中断信号给 CPU. CPU 可以去干别的, 收到中断再回来处理数据
- 进步: 实现了 CPU 和 I/O 设备之间的并行工作
- 缺点: 每次只能传一个字(Byte/Word), 数据量大时中断太频繁, CPU 还是累
- DMA (Direct Memory Access):
- 特点: 直接内存存取. CPU 只需要告诉 DMA 控制器"传什么, 传多少, 送到哪", 剩下的搬运工作由 DMA 完成. 以数据块为单位传输
- 进步: CPU 仅在开始和结束时参与, 干预更少了
- 通道 (Channel):
- 特点: 通道是一个独立的处理机(弱智版的 CPU), 有自己的指令程序. CPU 只要发一条指令, 通道就能完成一整组复杂的 I/O 任务
- 进步: CPU 干预最少
(2) 缓冲管理
-
为什么要引入缓冲区:
- 缓和 CPU 和 I/O 设备速度不匹配的矛盾
- 降低对 CPU 的中断频率
- 提高 CPU 和 I/O 设备之间的并行性
-
缓冲类型:
- 单缓冲
- 双缓冲(乒乓缓冲)
- 循环缓冲
- 缓冲池(Buffer Pool, 公用缓冲)
(3) 设备分配与设备独立性
-
数据结构
- SDT (系统设备表): 整个系统一张, 记录所有设备状态
- DCT (设备控制表): 每个设备一张
- COCT (控制器控制表): 每个控制器一张
- CHCT (通道控制表): 每个通道一张
-
设备独立性
- 定义: 应用程序独立于具体使用的物理设备
- 核心实现:
- 用户在程序里使用 "逻辑设备名" (e.g.
/dev/printer) - 系统在实际执行时, 通过一张 LUT [逻辑设备表, LookUp Table], 把"逻辑设备名"映射到"物理设备名" (e.g.
Printer_01)
- 用户在程序里使用 "逻辑设备名" (e.g.
- 好处:
- 灵活性: 物理设备坏了, 系统换一台分配就行, 用户程序不用改
- 重定向: 输出可以方便地从打印机改到文件
(4) SPOOLing 技术 [假脱机]
- 作用: 将一台独占的物理设备(如打印机), 虚拟为多台逻辑设备, 允许多个进程共享它
- 系统组成:
- 输入/输出井: 在磁盘上开辟的区域, 用来暂存数据
- 输入/输出缓冲区: 在内存中, 用于数据中转
- 输入/输出进程: 专门负责搬运数据的进程
- 实例: 共享打印机原理
- 当用户进程请求打印时, 系统并不是真的把打印机分配给它
- 第一步: 系统在 "磁盘输出井" 中申请一个空闲区, 把要打印的数据填进去
- 第二步: 系统申请一张"打印请求表", 挂到打印队列上
- 结果: 用户进程觉得"打印好了", 继续执行. 实际上, 真正的打印机空闲时, 输出进程才会去磁盘捞数据打印
(5) 磁盘存储器管理 [Disk Management]
这部分考磁盘调度算法, 通常给一个磁道访问序列, 让你算平均寻道长度
磁盘访问时间
总时间 = 寻道时间 + 旋转延迟 + 传输时间
目标: 使磁盘的 "平均寻道时间" 最短
Note
寻道时间 (Seek Time): 磁头移动到指定磁道(柱面)的时间
这一部分最慢, 且占比最大, 所以优化的重点是它
调度算法
场景:
- 磁盘 = 一栋 200 层的大楼 (楼层编号 0 - 199)
- 磁头 = 送餐员 (当前他在 50 层)
- 请求 = 有人点了外卖
- 90 层 的住户点了
- 10 层 的住户点了
- 80 层 的住户点了
算法:
- FCFS: 先来先服务. first come, first serve
- 原则: 完全按照订单进来的时间顺序跑, 不带脑子
- 顺序: 50 -> 90 -> 10 -> 80
- SSTF: 最短寻道时间优先. shortest seek time first
- 原则: "谁离我最近, 我就先送谁"
- 顺序: 50 -> 80 -> 90 -> 10
- SCAN: 扫描. elevator
- 原则: 即使离他最近的是楼下, 但如果电梯现在的设定是 "正在上行", 那它就必须一条道走到黑. 直到顶楼, 再回头
- 顺序: 50 -> 80 -> 90 -> 199 -> 10 -> 0
- C-SCAN: 扫描. elevator + 瞬移
- 原则: 单向移动. 到了头直接瞬间飞回起点(不反向服务), 重新开始扫描
- 顺序: 50 -> 80 -> 90 -> 199 -> 0 -> 10
