操作系统期中
一站式复习
覆盖 Lect01-13 全部考点,按"课件知识点 → 概念辨析 → 自测巩固"递进。基于北京大学操作系统(荣誉课程)2026 春课件。
📊 考点权重(基于课件结构)整理分析
同步机制
锁、信号量、管程、条件变量、读者-写者问题、死锁检测与预防
CPU 调度
FCFS/RR/SJF/SRTF/MLFQ/EDF、Linux CFS、调度策略比较
进程与线程
线程模型、fork/exec/wait、PCB/TCB、上下文切换、进程 vs 线程
抽象 & 概念
文件抽象、管道/Socket、IPC、地址空间、虚拟内存、OS 四大概念
🗺️ 考点路线图 整理导图
OS 概述与四大概念(Lect01-02)
OS 定义(Referee/Illusionist/Glue)、Thread/Address Space/Process/Dual Mode
线程与进程(Lect03)
pthreads、fork/exec/wait、线程状态、并发 vs 并行、竞态条件、临界区
文件与 I/O(Lect04-05)
文件描述符、FILE* 流、管道、Socket、客户端-服务器模式
同步基础(Lect06)
线程生命周期、上下文切换、调度循环、原子操作、互斥、临界区
锁与信号量(Lect07)
生产者-消费者问题、有界缓冲区、P/V 操作、信号量模式
原子指令与管程(Lect08)
test&set、compare&swap、Mesa vs Hoare 管程、条件变量
读者-写者问题(Lect09)
Monitor 解法、状态变量、优先级策略、单/多条件变量
经典调度(Lect10)
FCFS/RR/Priority/SJF/SRTF、CPU Burst 模型、调度目标
高级调度(Lect11)
Lottery/MLFQ/EDF/Linux CFS、饥饿/优先级反转/优先级捐赠
死锁(Lect12)
四大条件、资源分配图、检测算法、Banker's 算法、预防/恢复
第 1 章 · 操作系统概述(Lect01)
1.1 什么是操作系统?
操作系统是介于应用软件与硬件之间的特殊软件层,提供:
- 方便的抽象:隐藏复杂硬件设备细节
- 受保护的资源访问:管理共享资源的隔离与分配
- 安全与认证:控制谁可以访问什么
- 通信机制:逻辑实体间的通信
1.2 OS 的三个角色
| 角色 | 职责 | 例子 |
|---|---|---|
| Referee(裁判) | 管理保护、隔离和资源共享 | 资源分配、通信 |
| Illusionist(魔术师) | 提供干净、易用的物理资源抽象 | 无限内存、专用机器假象、虚拟化 |
| Glue(胶水) | 提供通用服务 | 存储、窗口系统、网络、共享、授权 |
1.3 OS 历史简谱
- Multics → AT&T Unix → BSD Unix → Ultrix, SunOS, NetBSD, FreeBSD
- Mach (微内核) + Unix BSD → NextStep → XNU → macOS / iOS
- MINIX → Linux → Android, Chrome OS, Ubuntu, Fedora, Debian
- CP/M → QDOS → MS-DOS → Windows 3.1 → NT → ... → Windows 10/11
第 2 章 · OS 四大核心概念(Lect02)
2.1 四个基本概念
| 概念 | 定义 | 关键内容 |
|---|---|---|
| Thread(线程) | 单一执行上下文,完全描述程序状态 | PC、寄存器、执行标志、栈 |
| Address Space(地址空间) | 程序在独立于物理内存的地址空间中执行 | 地址翻译、虚拟内存 |
| Process(进程) | 执行中程序的实例,包含地址空间 + 一个或多个线程 | 隔离、保护 |
| Dual Mode(双模式) | 只有"系统"能访问某些资源 | 用户态 vs 内核态、保护 |
2.2 Base and Bound(基址-界限保护)
简单的地址翻译机制:
- 物理地址 = 虚拟地址 + Base(基址寄存器)
- 如果虚拟地址 ≥ Bound(界限寄存器)→ 越界异常
- 保护 OS 和进程间隔离,不需要重定位加载器
第 3 章 · 线程与进程(Lect03)
3.1 线程(Thread)
- 定义:OS 提供的并发单元,每个线程代表一个任务
- 动机:处理多任务、网络服务器、并行程序、UI 响应、隐藏 I/O 延迟
- 三状态:RUNNING(运行中)、READY(就绪)、BLOCKED(阻塞等待 I/O)
- 共享状态:内存内容(全局变量、堆)、I/O 状态(文件描述符、网络连接)
- 私有状态:TCB(线程控制块)、CPU 寄存器(含 PC)、执行栈
3.2 并发 vs 并行
Parallelism(并行):同时做多件事(doing multiple things simultaneously)
单核上两个线程并发执行但不并行;多核上可以既并发又并行。
3.3 多处理 vs 多道程序
- Multiprocessing:多个 CPU/核
- Multiprogramming:多个作业/进程
- Multithreading:多个线程/进程
3.4 pthreads API
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
void pthread_exit(void *value_ptr);
int pthread_join(pthread_t thread, void **value_ptr);
3.5 进程(Process)
- 定义:具有受限权限的执行环境 = 地址空间 + 一个或多个线程
- 拥有文件描述符、网络连接等资源
- 进程间互相保护;OS 保护不受进程侵害
- 在现代 OS 中,内核之外的一切都在进程中运行
3.6 进程管理 API
| 系统调用 | 功能 |
|---|---|
fork() | 复制当前进程(返回 0=子进程,>0=父进程得到子进程 pid) |
exec() | 替换当前进程运行的程序(不返回如果成功) |
wait() | 等待子进程结束 |
exit() | 终止进程 |
kill() | 向另一个进程发送信号 |
sigaction() | 设置信号处理函数 |
3.7 fork() 关键特性
- fork() 后父进程和子进程的地址空间(内存)、文件描述符等全部复制
- 但两者独立运行,调度顺序不确定(竞态)
- 不要在 fork() 后直接做复杂操作而不 exec()(多线程进程中尤其危险——其他线程会消失)
3.8 进程 vs 线程
| 同一进程 | 不同进程 | |
|---|---|---|
| 切换开销 | 低 (~100 ns) | 高 (~3-4 μs) |
| 保护 | 低 | 高 |
| 共享开销 | 低 | 中/高 |
| 并行 | Yes(多核) | Yes |
3.9 竞态条件(Race Condition)
例子:Thread A: x = y + 1; Thread B: y = 2; y = y * 2; → x 可能为 1, 3 或 5。
第 4 章 · 文件与 I/O(Lect04)
4.1 "Everything is a File" 哲学
Unix/POSIX 中,磁盘文件、设备、网络 Socket、管道等使用统一接口:open()、read()、write()、close()。
4.2 文件系统抽象
- 文件:命名的数据集合(字节序列),含元数据(大小、修改时间、所有者)
- 目录:包含文件和子目录的"文件夹",层次命名(路径)
- 绝对路径(
/home/ff/pkuos)vs 相对路径(./,../,~/)
4.3 I/O 层次(自底向上)
- 磁盘 / Flash / 控制器 / DMA
- I/O 驱动(命令与数据传输)
- 文件系统(文件/目录/索引)
- Open File Description(syscall 层)
- 文件描述符(低层 I/O)
- Streams(高层 I/O,
FILE*)
4.4 文件描述符(Low-Level I/O)
| 函数 | 功能 |
|---|---|
int open(path, flags, mode) | 打开文件,返回文件描述符 |
ssize_t read(fd, buf, max) | 读取最多 max 字节(0=EOF, -1=错误) |
ssize_t write(fd, buf, size) | 写入 size 字节 |
off_t lseek(fd, offset, whence) | 移动文件偏移(SEEK_SET/END/CUR) |
int close(fd) | 关闭文件描述符 |
标准描述符:STDIN_FILENO (0)、STDOUT_FILENO (1)、STDERR_FILENO (2)
4.5 FILE* 流(High-Level I/O)
FILE*结构包含:文件描述符 + 用户态缓冲区 + 锁fopen/fclose、fread/fwrite、fprintf/fscanf、fseek/ftell- 三个预定义流:
stdin、stdout、stderr - 缓冲原因:系统调用比函数调用昂贵得多;内核不理解格式化
4.6 fork() 与文件描述符
父进程 read(3, buf, 100) 后子进程 read(3, buf, 100),两者推进同一个文件偏移。
第 5 章 · IPC、管道与 Socket(Lect05)
5.1 进程间通信(IPC)
进程间天然隔离;通信需要在保护墙上"打洞"。两种方式:
- 文件:持久存储,但昂贵(需写磁盘)
- 内核内存队列:通过系统调用实现的管道 / Socket
5.2 Unix 管道(Pipe)
int pipe(int fileds[2]):fileds[0] 读端,fileds[1] 写端- 实现为固定大小队列(有界缓冲区)
- 写满则阻塞(生产者睡眠),读空则阻塞(消费者睡眠)
- 单向、单机、需通过 fork() 继承文件描述符
5.3 Socket(套接字)
- 通信端点,外观类似文件 I/O(有文件描述符)
- 双向(两个队列,每个方向一个)
- 进程可在不同机器上,无需共同祖先
- 同一抽象适用于本地、TCP/IP、UDP/IP
5.4 管道 vs Socket
| 管道 | Socket | |
|---|---|---|
| 方向 | 单向(单队列) | 双向(双队列) |
| 范围 | 仅同机 | 同机或跨机 |
| 建立方式 | pipe() → fork() 继承 | socket/bind/connect/listen/accept |
| 接口 | read/write | read/write(有些操作不支持如 lseek) |
5.5 Socket 客户端-服务器模式
服务器端:socket() → bind() → listen() → accept() → read/write → close()
客户端:socket() → connect() → write/read → close()
TCP 连接由 5 元组标识:(源IP, 源端口, 目的IP, 目的端口, 协议)
5.6 并发服务器设计模式
| 模式 | 特点 |
|---|---|
| v1 Sequential | 单进程,一次处理一个连接 |
| v2 fork (protection) | 每连接 fork,父进程 wait 子进程 |
| v3 fork (concurrent) | 每连接 fork,父进程不 wait(并发) |
| v4 threads | 每连接新线程(效率高,保护弱) |
| Thread Pool | 固定大小线程池,master 分发连接 |
管道是单向的(fileds[1] 写 → fileds[0] 读),仅限同机,缓冲区满时 write() 阻塞(不返回错误)。
第 6 章 · 并发与同步基础(Lect06)
6.1 进程控制块(PCB)
- 内核中每个进程/线程的表示结构
- 包含:状态(running/ready/blocked)、寄存器状态、PID、优先级、执行时间、内存空间等
6.2 线程/进程生命周期
状态转换:new → ready → running → waiting(等待事件)→ ready → ... → terminated
6.3 上下文切换(Context Switch)
关键:忘记保存/恢复任何一个寄存器 → 间歇性故障。
6.4 调度循环(Dispatch Loop)
Loop {
RunThread();
ChooseNextThread(); // 调度策略
SaveStateOfCPU(curTCB); // 保存上下文
LoadStateOfCPU(newTCB); // 恢复上下文
}
6.5 内部事件 vs 外部事件
| 内部事件 | 外部事件 | |
|---|---|---|
| 触发 | 线程自愿放弃 CPU(I/O 阻塞、yield) | 中断(硬件/软件信号) |
| 典型 | 等待 I/O、等待信号 | 定时器中断、设备中断 |
| 作用 | 线程主动让出 | 确保调度器能重新获得控制权 |
6.6 同步核心定义
| 术语 | 定义 |
|---|---|
| 原子操作 | 要么完全执行,要么完全不执行;不可分割 |
| 同步 | 使用原子操作确保线程间的协调 |
| 互斥 | 确保同一时间只有一个线程做某件事 |
| 临界区 | 一次只能有一个线程执行的代码段 |
| 锁 | 一次只能由一个线程持有的对象,提供互斥 |
历史教训:Therac-25(辐射过量)、Mars Pathfinder(优先级反转)、Toyota(失控加速)。
第 7 章 · 锁与信号量(Lect07)
7.1 锁的基本操作
Lock.acquire()— 等待直到锁空闲,然后标记为忙Lock.release()— 标记锁为空闲- 只有持有锁的线程才能释放锁
7.2 信号量(Semaphore)
P() / down() / wait():等待信号量 > 0,然后减 1
V() / up() / signal():加 1,唤醒一个等待的 P(如果有)
"P" = "proberen"(测试),"V" = "verhogen"(增加),荷兰语。
7.3 信号量的两种模式
| 模式 | 初值 | 用法 |
|---|---|---|
| 互斥(Mutex) | 1 | P(mutex); 临界区; V(mutex) |
| 信号/同步 | 0 | 等待者 P(sem); 通知者 V(sem) |
7.4 生产者-消费者(有界缓冲区)
Semaphore fullSlots = 0;
Semaphore emptySlots = bufSize;
Semaphore mutex = 1;
Producer(item) {
P(&emptySlots); // 等待空位
P(&mutex); // 获取互斥
Enqueue(item); // 临界区
V(&mutex);
V(&fullSlots); // 通知消费者
}
Consumer() {
P(&fullSlots); // 等待数据
P(&mutex); // 获取互斥
item = Dequeue(); // 临界区
V(&mutex);
V(&emptySlots); // 通知生产者
return item;
}
• P 操作的顺序很重要(先 P(mutex) 再 P(fullSlots) 会死锁!)
• V 操作的顺序不重要(仅影响调度效率)
• 为每个约束使用独立的信号量
• 不对称性:生产者 P(emptySlots)/V(fullSlots),消费者 P(fullSlots)/V(emptySlots)
第 8 章 · 原子指令与管程(Lect08)
8.1 锁的实现:禁用中断
8.2 原子 Read-Modify-Write 指令
| 指令 | 行为 | 体系结构 |
|---|---|---|
test&set(&addr) | 返回旧值,设置 addr = 1 | 多数体系 |
swap(&addr, reg) | 交换寄存器与内存值 | x86 |
compare&swap(&addr, r1, r2) | 若 *addr == r1,则 *addr = r2 | 68000 等 |
硬件负责在单处理器和多处理器上的正确性。
8.3 基于 test&set 的简单锁(忙等)
int value = 0;
Acquire() { while (test&set(value)); } // 忙等
Release() { value = 0; }
问题:忙等浪费 CPU;可能导致优先级反转;多处理器上引起缓存乒乓效应。
8.4 管程(Monitor)
锁:提供对共享数据的互斥访问。
条件变量:临界区内等待某条件的线程队列。
关键思想:允许在临界区内睡眠——原子性地释放锁并进入睡眠。
8.5 条件变量操作
Wait(&lock):原子性地释放锁并睡眠;返回前重新获取锁Signal():唤醒一个等待者(如果有)Broadcast():唤醒所有等待者- 规则:进行条件变量操作时必须持有锁
8.6 Mesa vs Hoare 管程
| Hoare | Mesa | |
|---|---|---|
| Signal 后 | Signal 者交出锁和 CPU,等待者立即运行 | Signal 者保持锁和 CPU,等待者放入就绪队列 |
| Wait 返回后 | 条件一定成立 | 条件可能已被改变 → 必须用 while 循环 |
| 效率 | 强制上下文切换(低效) | 更高效、更容易实现 |
| 使用 | 多数教科书 | 多数实际 OS |
8.7 信号量 vs 条件变量
条件变量没有记忆(如果没有等待者,Signal 丢失)。
第 9 章 · 读者-写者问题(Lect09)
9.1 问题定义
共享数据库,两类用户:
- 读者(Readers):只读,不修改。允许多个读者同时访问。
- 写者(Writers):读写。同一时间只能有一个写者,且不能有读者。
9.2 正确性约束
- 没有写者时,读者可以访问数据库
- 没有读者或写者时,写者可以访问数据库
- 同一时间只有一个线程操作状态变量
9.3 状态变量
| 变量 | 含义 | 初始值 |
|---|---|---|
| AR | 活跃读者数 | 0 |
| WR | 等待读者数 | 0 |
| AW | 活跃写者数 | 0 |
| WW | 等待写者数 | 0 |
两个条件变量:okToRead、okToWrite。所有状态变量由一个锁保护。
9.4 读者代码
Reader() {
acquire(&lock);
while ((AW + WW) > 0) { // 有写者存在 → 等待
WR++;
cond_wait(&okToRead, &lock);
WR--;
}
AR++;
release(&lock);
AccessDatabase(ReadOnly); // 实际读操作
acquire(&lock);
AR--;
if (AR == 0 && WW > 0) // 最后一个读者,唤醒写者
cond_signal(&okToWrite);
release(&lock);
}
9.5 写者代码
Writer() {
acquire(&lock);
while ((AW + AR) > 0) { // 有活跃用户 → 等待
WW++;
cond_wait(&okToWrite, &lock);
WW--;
}
AW++;
release(&lock);
AccessDatabase(ReadWrite); // 实际写操作
acquire(&lock);
AW--;
if (WW > 0) // 优先给写者
cond_signal(&okToWrite);
else if (WR > 0) // 否则唤醒所有读者
cond_broadcast(&okToRead);
release(&lock);
}
读者可能饥饿——如果写者持续到来。
如果只有一个条件变量——必须用 broadcast() 而非 signal()。
第 10 章 · 经典调度策略(Lect10)
10.1 CPU Burst 模型
程序在 CPU Burst(使用 CPU)和 I/O Burst(等待 I/O)之间交替。CPU Burst 倾向于小 Burst。每次调度决策是关于下一个 CPU Burst给哪个作业。
10.2 调度目标
| 目标 | 含义 |
|---|---|
| 最小化完成时间 | 操作/作业的经过时间(用户感知) |
| 最大化吞吐量 | 每秒操作/作业数(最小化开销,高效资源利用) |
| 公平性 | 用户间公平共享 CPU |
10.3 经典调度策略对比
| 策略 | 特点 | 优点 | 缺点 |
|---|---|---|---|
| FCFS/FIFO | 先来先服务,运行直到阻塞 | 简单 | 短作业被长作业阻塞(队头阻塞) |
| Round Robin (RR) | 时间片轮转,q 时间后抢占 | 公平、短作业友好 | 上下文切换开销 |
| Strict Priority | 总是运行最高优先级 | 保证重要任务 | 低优先级可能饥饿 |
| SJF(非抢占) | 运行最短作业 | 最优平均完成时间 | 需预测未来、不公平 |
| SRTF(抢占版 SJF) | 运行剩余时间最短的 | 抢占中最优平均完成时间 | 需预测、长作业饥饿 |
10.4 Round Robin 关键参数
- 时间片 q:q 太大 → 接近 FCFS;q 太小 → 上下文切换开销主导
- n 个进程,最大等待 ≤ (n-1)q
- 典型值:10-100ms
10.5 SRTF 是最优的
SRTF 是抢占中可证明最优的平均完成时间调度。
预测:τn = α·tn-1 + (1-α)·τn-1(指数平均)
第 11 章 · 高级调度(Lect11)
11.1 Lottery Scheduling(彩票调度)
- 给每个作业分配彩票票数;每次时间片随机抽取中奖票
- 平均 CPU 时间与票数成正比
- 近似 SRTF:短作业多给票;每个作业至少 1 票(无饥饿)
- 优点:负载变化时优雅地调整比例
11.2 Multi-Level Feedback Queue (MLFQ)
- 多队列,不同优先级;高优先级 = "前台"任务
- 每队列有独立调度算法(前台 RR,后台 FCFS)
- 量子指数增长(1ms, 2ms, 4ms, ...)
- 调整规则:作业从最高优先级开始;用完时间片 → 降级;主动放弃 CPU(I/O) → 升回
- 结果:近似 SRTF——CPU 密集型作业快速降级,I/O 密集型作业留在顶部
11.3 Linux CFS(完全公平调度器)
- 追踪每个线程的 CPU 时间;选择 CPU 时间最小的线程运行
- 近似"理想"多任务处理器(公平队列)
- O(log N) 使用堆实现
- 目标延迟:每个进程在此周期内获得服务(如 20ms)
- 最小粒度:最小时间片长度(如 1ms)——防止过度上下文切换
- 睡眠线程不推进 CPU 时间 → 醒来时获得提升(自动交互性)
11.4 EDF(最早截止时间优先)
- 实时调度:优先级基于绝对截止时间 Dabs = Dold + Pi
- 总是调度绝对截止时间最近的活动任务
- 可行性测试:Σ(Ci / Di) ≤ 1
11.5 饥饿 vs 死锁
死锁(Deadlock):资源的循环请求(没有外部干预无法结束)
死锁 → 饥饿,但饥饿 ⇏ 死锁。
11.6 优先级反转与优先级捐赠
高优先级任务被低优先级任务阻塞(因为锁)。中等优先级任务饥饿高优先级任务。
解决:优先级捐赠/继承——高优先级线程临时将优先级授予低优先级锁持有者。
著名案例:Mars Pathfinder 因优先级反转导致系统重置,启用优先级捐赠后修复。
11.7 调度策略选择指南
| 目标 | 推荐调度器 |
|---|---|
| CPU 吞吐量 | FCFS |
| 平均完成时间 | SRTF 近似(MLFQ) |
| I/O 吞吐量 | SRTF 近似 |
| 公平性(CPU 时间) | Linux CFS |
| 公平性(等待时间) | Round Robin |
| 满足截止时间 | EDF |
| 偏袒重要任务 | Priority |
第 12 章 · 死锁(Lect12)
12.1 死锁的定义
线程 A 持有资源 1,等待资源 2;线程 B 持有资源 2,等待资源 1 → 循环等待。
12.2 死锁的四个必要条件
2. 持有并等待(Hold and Wait):线程持有至少一个资源,同时等待其他资源
3. 不可抢占(No Preemption):资源只能被持有者自愿释放
4. 循环等待(Circular Wait):存在集合 {T1,...,Tn},Ti 等待 Ti+1 持有的资源
四个条件必须同时满足才可能死锁。
12.3 资源分配图
- 节点:线程 (T) 和资源类型 (R, 含 Wi 个实例)
- 请求边:Ti → Rj(线程请求资源)
- 分配边:Rj → Ti(资源分配给线程)
- 有环不一定死锁(如果有多实例);环是必要条件不是充分条件
12.4 死锁的四种处理方式
| 方式 | 策略 |
|---|---|
| 预防(Prevention) | 打破四个条件之一。如:强制排序(总是先获取 x 再 y)、一次性请求所有资源 |
| 恢复(Recovery) | 让死锁发生再恢复。终止线程、抢占资源、回滚操作(数据库常用) |
| 避免(Avoidance) | 动态延迟资源请求。如 Banker's 算法,保持系统在安全状态 |
| 忽略(Ostrich) | 假装死锁不会发生(大多数 OS 的做法) |
12.5 Banker's 算法
- 提前声明最大资源需求
- 允许线程继续当且仅当:(available - requested) ≥ 任何线程可能需要的最大剩余量
- 如果结果为无死锁,则批准请求;否则拒绝
- 保持系统在安全状态
12.6 死锁检测算法
[Avail] = [FreeResources]
UNFINISHED = 所有节点
do {
done = true
For each node in UNFINISHED {
if ([Request_node] <= [Avail]) {
remove node from UNFINISHED
[Avail] = [Avail] + [Alloc_node]
done = false
}
}
} until(done)
// UNFINISHED 中剩余的节点 → 死锁
12.7 哲学家(律师)就餐问题
- 5 根筷子、5 个哲学家,需要 2 根才能吃
- 如果所有人同时拿左边 → 死锁
- 修复:不允许拿最后一根筷子(除非某饥饿哲学家已有另一根)
第 13 章 · 现代系统调度(Lect13)
13.1 ZygOS(SOSP'17)— FCFS 启发
- 问题:数据中心中微秒级 RPC 的尾延迟
- 洞察:单队列模型因瞬态负载不平衡在 SLO 上有更好的吞吐
- 方案:工作窃取(Work-Stealing)在数据平面中实现工作守恒
- 结果:比 Linux 快 1.63×,99 分位延迟降低 3.68×
13.2 Shinjuku(NSDI'19)— RR 启发
- 问题:微秒级抢占调度以实现低尾延迟
- 方案:5μs 一次的抢占、专用调度核、硬件虚拟化支持快速抢占
13.3 Tiresias(NSDI'19)— SJF/SRTF/MLFQ 启发
- 问题:无完整作业信息的 GPU 集群管理器
- 方案:二维基于年龄的调度(2DAS)——按已执行的 GPU 时间优先调度最短作业
- 结果:平均 JCT 比 YARN-CS 提升 5.5×
13.4 Dominant Resource Fairness (DRF)
- 问题:多资源类型的公平共享(用户需求异构)
- 主导资源:用户份额最大的资源
- 主导份额:主导资源被分配的比例
- 对主导份额应用 max-min 公平
- 属性:份额保证 ✓、策略证明 ✓、帕累托效率 ✓、无嫉妒 ✓
13.5 经典与现代映射
| 经典策略 | 现代系统 |
|---|---|
| FCFS | ZygOS (SOSP'17) |
| RR | Shinjuku (NSDI'19) |
| SJF, SRTF, MLFQ | Tiresias (NSDI'19) |
| 公平性 | DRF (NSDI'11), FairRide (NSDI'16) |
互动自测(打分)DeepSeek 生成
选择题自测
共 30 题优缺点对比总结 课件整理
以下内容均来自课件中的对比讨论,帮助快速记忆各方案/机制的优劣。
🧵 进程 vs 线程
| 进程 | 线程 | |
|---|---|---|
| 优点 | 强隔离保护;崩溃不影响其他进程;独立地址空间 | 切换开销低 (~100ns);共享内存方便通信;创建快 |
| 缺点 | 切换开销高 (~3-4μs);共享开销高(IPC 需内核介入) | 保护弱(一个线程崩溃可能影响整个进程);同步复杂 |
📡 管道 vs Socket
| 管道 | Socket | |
|---|---|---|
| 优点 | 简单;通过 fork 继承即可建立;无需寻址 | 双向通信;可跨机器;标准网络 API;灵活的 C/S 模式 |
| 缺点 | 单向(需两根管道才能双向);仅限同机;需共同祖先 | 建立过程复杂(socket/bind/listen/accept/connect);需要地址和端口管理 |
🔒 同步机制对比
| 机制 | 优点 | 缺点 |
|---|---|---|
| Lock (mutex) | 简单直观;互斥保证强 | 只能互斥;不能表达复杂条件等待 |
| Semaphore | 有记忆(V 不会丢失);可做互斥也可做信号;灵活 | P 操作顺序错误易死锁;代码分散不易维护 |
| Monitor + CV | 临界区内可睡眠(原子释放锁);代码结构化;支持 broadcast | CV 无记忆(Signal 无等待者则丢失);Mesa 需 while 循环重检 |
🏗️ 锁实现方式对比
| 实现 | 优点 | 缺点 |
|---|---|---|
| 禁用中断 | 简单(单处理器上) | 用户态不能做;临界区长会丢 I/O;多处理器不工作 |
| test&set 忙等 | 硬件保证原子性;多处理器可用 | 忙等浪费 CPU;缓存乒乓效应;可能导致优先级反转 |
| test&set + 睡眠 | 短忙等 + 长等待时睡眠,减少 CPU 浪费 | 实现更复杂;需管理等待队列 |
🏫 Mesa vs Hoare 管程
| Hoare | Mesa | |
|---|---|---|
| 优点 | Wait 返回后条件一定成立(if 即可);语义清晰 | 实现效率高;无需强制上下文切换;实际 OS 广泛使用 |
| 缺点 | 强制上下文切换低效;实现复杂 | Wait 返回后条件可能已变(需 while 循环);程序员易写错 |
⏱️ 调度策略对比
| 策略 | 优点 | 缺点 |
|---|---|---|
| FCFS | 简单;无上下文切换开销;对全等长作业最优吞吐 | 队头阻塞(短作业被长作业卡住);平均等待时间差 |
| Round Robin | 公平;短作业友好;最大等待有上界 | 上下文切换开销;q 太小吞吐差;q 太大退化为 FCFS |
| SJF / SRTF | 最优平均完成时间(SJF 非抢占,SRTF 抢占);I/O 吞吐好 | 需预测未来(不准);长作业可能饥饿;不公平 |
| Priority | 保证重要任务优先;可配合其他策略 | 低优先级可能饥饿;优先级反转问题 |
| MLFQ | 自适应(近似 SRTF);无需预知作业长度;兼顾交互与批处理 | 可被欺骗(加无意义 I/O 保持高优先级);参数调优复杂 |
| Lottery | 概率公平;负载变化自适应;无饥饿(每作业至少 1 票) | 短期不公平(随机性);不适合硬实时 |
| Linux CFS | 公平(CPU 时间);自动交互性(睡眠线程获提升);O(log N) | 对实时任务不够好;不保证截止时间 |
| EDF | 可证明最优满足截止时间(若 ΣC/D ≤ 1);动态适应 | 需预知计算时间和周期;过载时行为不可预测 |
💀 死锁处理方式对比
| 方式 | 优点 | 缺点 |
|---|---|---|
| 预防 (Prevention) | 从根本上杜绝死锁 | 限制严格(强制排序等),降低并发性和资源利用率 |
| 避免 (Avoidance) | 动态判断,最大限度利用资源 | 需预知最大需求(不现实);Banker's 算法开销大 |
| 恢复 (Recovery) | 允许死锁发生再处理,前期无限制 | 恢复代价大(终止线程、回滚);数据可能丢失 |
| 忽略 (Ostrich) | 零开销;大多数场景死锁概率低 | 死锁发生时系统僵死;不可用于关键系统 |
🖥️ 并发服务器模式对比
| 模式 | 优点 | 缺点 |
|---|---|---|
| Sequential | 简单;无同步问题 | 一次只能处理一个连接;其他连接等待 |
| fork (per connection) | 强隔离(进程);并发处理 | fork 开销大;大量连接时进程数爆炸 |
| Thread per connection | 低开销;共享地址空间方便 | 保护弱;线程数过多时调度开销大 |
| Thread Pool | 控制资源使用;避免频繁创建/销毁线程 | 线程数固定时突发流量排队;调优池大小需经验 |
⚛️ 信号量 vs 条件变量
| 信号量 | 条件变量 | |
|---|---|---|
| 记忆性 | 有记忆:V 操作增加计数,后续 P 可匹配 | 无记忆:Signal 时无等待者则丢失 |
| 使用场景 | 资源计数、简单互斥、生产者-消费者 | 复杂条件等待(需在临界区内睡眠时) |
| 优点 | 灵活;两种模式(互斥+同步) | 结构清晰;配合 Monitor 使用;支持 broadcast |
| 缺点 | P 顺序错→死锁;代码分散;不易维护 | 必须配合锁使用;Mesa 语义下需 while 重检 |
📂 高层 I/O (FILE*) vs 低层 I/O (fd)
| FILE* 流 (High-Level) | 文件描述符 (Low-Level) | |
|---|---|---|
| 优点 | 用户态缓冲(性能好);格式化 I/O;方便的行/块操作 | 无额外缓冲层;行为可预测;与 fork/dup 等配合直观 |
| 缺点 | 与 fd 混用会读到错误数据;需 fflush 保证持久化 | 逐字节读性能差;无格式化支持;需手动管理缓冲区 |
考前速查表 整理速查
🔑 同步机制对比
| 机制 | 操作 | 特点 | 适用场景 |
|---|---|---|---|
| Lock | acquire / release | 互斥、一次一个线程 | 临界区保护 |
| Semaphore | P(down) / V(up) | 计数、有记忆、可作锁或信号 | 生产者-消费者、资源计数 |
| Monitor | Wait / Signal / Broadcast | 锁+条件变量、临界区内睡眠 | 复杂同步(读者-写者) |
| Condition Var | Wait / Signal / Broadcast | 无记忆(Signal 丢失无等待者) | 配合 Monitor 使用 |
🔄 调度策略速查
| 策略 | 抢占 | 最优性 | 饥饿风险 |
|---|---|---|---|
| FCFS | 否 | — | 无限循环阻塞所有 |
| RR | 是 | — | 无(最大等待 ≤ (n-1)q) |
| SJF | 否 | 最优平均完成时间 | 长作业可能饥饿 |
| SRTF | 是 | 抢占中最优平均完成时间 | 长作业可能饥饿 |
| Priority | 可配 | — | 低优先级可能饥饿 |
| MLFQ | 是 | 近似 SRTF | 近似 SRTF |
| EDF | 是 | 满足截止时间 | —(如果 ΣC/D ≤ 1) |
| CFS | 是 | 公平(CPU 时间) | 无 |
⚠️ 死锁四大条件与对策
| 条件 | 打破方式 |
|---|---|
| 互斥 | 虚拟化资源(如假脱机打印) |
| 持有并等待 | 一次性请求所有资源 |
| 不可抢占 | 允许抢占资源(回滚+重试) |
| 循环等待 | 强制资源排序(总是先获取 x 再 y) |
📋 Mesa vs Hoare 管程
| Hoare | Mesa(实际使用) | |
|---|---|---|
| Signal 语义 | Signal 者放弃 CPU,等待者立即运行 | Signal 者保持 CPU |
| Wait 返回后 | 条件一定成立(if 检查) | 条件可能已变(while 检查) |
📝 fork() vs exec() vs wait()
| 调用 | 行为 | 返回 |
|---|---|---|
fork() | 复制当前进程(地址空间+文件描述符) | 父进程:子 pid;子进程:0;出错:-1 |
exec() | 替换当前进程的程序 | 成功不返回;失败返回 -1 |
wait(&status) | 等待子进程结束 | 结束的子进程 pid |
🧵 线程共享 vs 私有
| 共享(所有线程) | 私有(每线程) |
|---|---|
| 全局变量、堆、代码 | TCB、寄存器(含 PC)、栈 |
| 文件描述符、网络连接 | 线程元数据 |