第 2 章 进程与线程
408 操作系统第 2 章 进程与线程复习讲义,涵盖核心概念、重点考点与典型题型。
Last updated on
可以先把整章压缩成一条主线:
有很多程序想同时运行 → 用进程/线程描述它们 → CPU 不够用所以需要调度 → 并发访问共享资源会出错所以要同步互斥 → 同步和资源申请不当又可能产生死锁。
所以第 2 章其实是一条非常完整的因果链。
| 模块 | 重要度 | 主要考法 |
|---|---|---|
| 进程、PCB、状态转换 | ★★★★★ | 选择题高频 |
| 线程、用户级/内核级线程 | ★★★★☆ | 选择题 |
| CPU 调度算法 | ★★★★★ | 选择 + 计算 |
| 上下文切换 | ★★★★☆ | 选择题 |
| 同步、互斥、临界区 | ★★★★★ | 选择 + PV 基础 |
| 信号量、PV 操作 | ★★★★★ | 综合题核心 |
| 生产者消费者、读写者 | ★★★★★ | 综合题核心 |
| 死锁四条件 | ★★★★★ | 选择题 |
| 银行家算法 | ★★★★★ | 计算/综合题 |
| 死锁检测解除 | ★★★★☆ | 选择 + 判断 |
题库中也确实反复覆盖状态转换、线程、CPU 调度、互斥实现、信号量及死锁等内容。
一、进程:为什么需要它?
1. 程序和进程不是一个东西
程序本质上是磁盘里的一份静态代码。
比如你磁盘里有:
chrome.exe它自己不会“运行”。
当你双击 Chrome 后,代码被加载,操作系统给它分配内存、CPU 时间、文件等资源,并记录它执行到了哪一条指令,这个正在进行的执行活动才叫进程。
资料中的正式表述是:
进程是进程实体的运行过程,是系统进行资源分配和调度的独立单位。
因此:
程序 = 静态代码
进程 = 程序 + 数据 + 当前执行状态 + 各种系统资源同一个程序可以对应多个进程。
例如开三个 Chrome 实例:
Chrome程序
↓
进程 P1
进程 P2
进程 P3它们可能执行同一份代码,但拥有不同的数据和执行状态。
二、进程实体:程序段 + 数据段 + PCB
这是408非常重要的基本结论:
其中最重要的是 PCB(Process Control Block,进程控制块)。
资料特别强调:
PCB 是进程存在的唯一标志。
为什么?
因为操作系统不是“看着你的代码”来管理进程,而是看 PCB。
例如进程突然被切走:
P1正在执行
PC = 1000
AX = 5
SP = 8000操作系统必须保存:
PCB(P1)
├─ 当前状态
├─ PC
├─ 寄存器
├─ 栈指针
├─ 优先级
├─ 内存信息
├─ 打开的文件
└─ I/O资源以后 P1 再运行,就从 PCB 中恢复现场。
因此可以形成一个非常重要的考试链:
创建进程
→ 创建PCB
进程切换
→ 保存/恢复PCB中的现场信息
撤销进程
→ 回收PCB和资源三、PCB 里面到底有什么?
资料将其归为四大类。
| 类型 | 典型内容 |
|---|---|
| 标识信息 | PID、PPID、UID |
| 调度信息 | 状态、优先级、CPU时间、阻塞原因 |
| 资源信息 | 内存、文件、I/O资源 |
| CPU现场 | PC、SP、PSW、通用寄存器 |
其中408最爱考的是最后一个:
进程切换时需要保存什么?
核心就是:
PC
通用寄存器
状态寄存器
栈指针
其他CPU上下文PCB 本身由内核维护,不属于普通用户进程的虚拟地址空间。
四、进程的虚拟地址空间
资料给出的典型布局可以理解成:
高地址
┌────────────┐
│ 栈 │ ↓增长
├────────────┤
│ 共享库映射 │
├────────────┤
│ │
│ 堆 │ ↑增长
├────────────┤
│ .bss │ 未初始化全局/静态变量
│ .data │ 已初始化全局/静态变量
├────────────┤
│ .rodata │ 只读数据
│ .text │ 程序代码
└────────────┘
低地址记忆核心:
| 对象 | 典型位置 |
|---|---|
| 函数代码 | .text |
| 字符串常量等 | .rodata |
| 已初始化全局/静态变量 | .data |
| 未初始化全局/静态变量 | .bss |
malloc() 动态对象 | 堆 |
| 局部变量、调用现场 | 栈 |
这也是题库直接涉及的考法。
五、进程状态转换——选择题超级高频
最核心的只有三个:
调度
就绪 ───────→ 运行
↑ │
│ │
事件完成│ │等待I/O/资源
│ ↓
└────────── 阻塞1. 就绪态
进程:
什么都有,只差 CPU。
这是判断题最有用的一句话。
CPU 给它:
就绪 → 运行2. 运行态
正在 CPU 上执行。
单 CPU 情况下:
任一时刻最多一个进程运行。
注意是“最多一个”,可以暂时没有用户进程运行。
3. 阻塞态
进程不是缺 CPU,而是缺别的条件。
例如:
等待磁盘I/O
等待键盘输入
等待信号量
等待消息所以非常重要:
给阻塞进程 CPU,它仍然不能运行。
六、四条最重要的状态转换
就绪 → 运行
原因:
被调度运行 → 就绪
典型原因:
时间片用完
被更高优先级进程抢占注意:
时间片用完不是阻塞!
这是408非常典型的陷阱。
运行 → 阻塞
典型原因:
等待I/O
P操作申请不到资源
等待消息
等待某事件这里有一个非常重要的规律:
阻塞通常是进程主动产生的。
例如程序主动执行:
read(...)
P(S)发现暂时无法继续,于是阻塞。
阻塞 → 就绪
发生等待事件:
I/O完成
资源到达
被V操作唤醒
消息到达这是被动发生的。
所以记:
运行 → 阻塞:主动
阻塞 → 就绪:被动资料明确将状态转换列为历年反复考查点。
七、哪些状态不能直接转换?
最典型:
就绪 → 阻塞通常不可能。
原因:
阻塞意味着:
“我正在运行,但发现条件不满足。”
可一个就绪进程根本还没有 CPU,它怎么执行 P 操作、I/O 系统调用之类的操作?
所以必须:
就绪 → 运行 → 阻塞同理:
阻塞 → 运行通常也不能直接发生。
必须:
阻塞 → 就绪 → 运行这类状态图判断题必须非常熟。
八、进程控制:创建、终止、阻塞、唤醒
操作系统通过原语完成这些关键操作。
原语强调:
执行过程中不可被中断,具有原子性。
创建进程
大体过程:
申请PCB
↓
分配PID
↓
分配资源
↓
初始化PCB
↓
进入就绪队列所以“创建完成”后的正常状态是:
就绪态不是创建态。
阻塞 Block
只有运行进程才能主动阻塞自己。
过程:
保存CPU现场
↓
运行 → 阻塞
↓
PCB进入相应等待队列
↓
重新调度CPU唤醒 Wakeup
一般是其他进程或中断处理程序完成:
找到阻塞PCB
↓
移出等待队列
↓
阻塞 → 就绪
↓
加入就绪队列注意:
唤醒 ≠ 立即运行。
只是:
阻塞 → 就绪是否能运行还取决于 CPU 调度。
九、进程通信 IPC
资料重点介绍四种。
| 方式 | 核心思想 |
|---|---|
| 共享内存 | 双方访问同一块内存 |
| 消息传递 | send/receive |
| 管道 | 内核缓冲区,按字节流传输 |
| 信号 | 通知某个事件发生 |
共享内存
速度非常高,因为映射后两个进程直接访问共同区域:
P1 ─┐
├→ Shared Memory
P2 ─┘但是:
共享内存只解决“怎么共享”,不自动解决“谁先访问”。
所以通常还需要:
信号量
互斥锁管道
可以理解为内核里的 FIFO 缓冲区:
写进程 → [管道缓冲区] → 读进程普通管道:
单向
FIFO
数据读走后消失管道空:
读进程阻塞管道满:
写进程阻塞资料将管道特点列为曾经考查的内容。
十、线程——进程和线程到底怎么区分?
这是本章另一个重要概念。
现代操作系统引入线程后:
这两句话必须牢牢记住。
同一进程内部:
进程
├─ 代码
├─ 数据
├─ 堆
├─ 文件
│
├─ 线程T1
├─ 线程T2
└─ 线程T3线程共享:
地址空间
代码
全局变量
堆
打开的文件但每个线程必须有自己的:
线程ID
程序计数器PC
寄存器
栈
TCB为什么栈不能共用?
因为:
T1 正在调用 f()
T2 正在调用 g()两个线程的函数调用链完全不同。
所以:
题库也专门考查线程共享资源与私有资源的区别。
十一、用户级线程 ULT 与内核级线程 KLT
这是选择题重点。
用户级线程
内核:
“我根本不知道你内部有很多线程。”
例如:
内核看到:
进程P
用户空间:
P → T1 T2 T3 T4线程调度由线程库完成。
优点:
切换快
不需要进入内核态
管理开销小
调度策略可自己设计最大缺陷:
某个线程执行阻塞系统调用,可能导致整个进程阻塞。
另一个重要缺陷:
多对一情况下无法让多个用户线程真正同时跑在多个 CPU 上。
内核级线程
内核明确知道:
T1
T2
T3内核可以分别调度。
因此:
T1 → CPU0
T2 → CPU1
T3 → CPU2可以真正并行。
而且:
T1阻塞并不意味着 T2、T3 必须跟着阻塞。
缺点:
创建、切换、调度需要内核参与,开销更大。
十二、多线程模型
非常适合选择题。
| 模型 | 映射 | 最大特点 |
|---|---|---|
| 多对一 | 多个 ULT → 一个 KLT | 快,但不能多核并行 |
| 一对一 | 一个 ULT → 一个 KLT | 并行性最好,开销大 |
| 多对多 | 多个 ULT → 多个 KLT | 折中 |
最简单的判断方式:
问:一个线程阻塞会不会拖死整个进程?
→ 看底下是不是只有一个内核线程。
问:能否真正多核并行?
→ 看是否存在多个可被内核调度的线程。十三、CPU 调度——本章第二个大重点
为什么调度?
因为:
就绪进程很多
CPU很少因此操作系统必须决定:
下一个让谁运行?
十四、三级调度
资料将调度分为三级。
| 调度 | 又名 | 做什么 | 频率 |
|---|---|---|---|
| 高级调度 | 作业调度 | 外存 → 内存 | 最低 |
| 中级调度 | 内存调度 | 内存 ↔ 外存 | 中等 |
| 低级调度 | 进程调度 | 就绪 → CPU | 最高 |
408最容易混:
分配 CPU 的不是作业调度,而是进程调度。
高级调度解决:
哪些作业有资格进入内存?低级调度解决:
已经在内存中的就绪进程,谁得到CPU?十五、抢占与非抢占
非抢占
一个进程获得 CPU 后:
别人不能强行抢走直到:
自己结束
或
自己阻塞抢占
如果发生:
更高优先级进程到来
时间片用完操作系统可以:
当前运行进程 → 就绪
新进程 → 运行现代分时系统一般需要抢占机制。
十六、调度性能指标——计算题必须会
周转时间
包括:
等待
CPU运行
I/O
其他系统时间平均周转时间
带权周转时间
如果结果为 5:
意味着它实际只需要运行 1 单位时间,却在系统中待了 5 单位时间。
等待时间
主要关注:
在就绪队列等待 CPU 的时间。
响应时间
特别重要于:
分时系统
交互系统资料明确把这些指标以及调度计算列为反复出现的考点。
十七、六类核心调度算法
1. FCFS 先来先服务
谁先来谁先跑。
P1 → P2 → P3特点:
非抢占
简单
公平缺陷:
护航效应(convoy effect):前面一个超长进程可以让后面的短进程全部排队。
因此:
有利于长作业
不利于短作业十八、SJF 短作业优先
从已经到达的进程中:
选运行时间最短的。
重要结论:
若所有作业同时到达且运行时间已知:
但有严重缺陷:
长进程可能饥饿。
例如:
长作业 L 等待
↓
短作业不断到来
S1 S2 S3 S4 ...
↓
L一直没机会抢占式 SJF 称:
最短剩余时间优先 SRTF。
十九、高响应比优先 HRRN
为了平衡:
FCFS:照顾老作业
SJF:照顾短作业定义响应比:
也可以理解成:
等待越久,响应比越来越高。
因此长作业不会无限饿死。
二十、优先级调度
核心:
谁优先级高谁运行可以:
抢占式
非抢占式又可以:
静态优先级
动态优先级动态优先级特别重要,因为可以解决饥饿。
例如:
等待越久
→ 优先级逐渐提高这个机制通常称为老化思想。
资料还强调一个常见设置原则:
I/O 型进程往往应比纯计算型进程具有更高优先级,这样可以让它尽快启动 I/O,提高 CPU 和设备并行程度。
二十一、RR 时间片轮转——分时系统核心
假设时间片:
q = 2ms队列:
P1 P2 P3则:
P1执行2ms
↓
没完成 → 队尾
P2执行2ms
↓
P3
↓
P1
...它本质上是:
FCFS + 抢占 + 时间片。
最重要的是时间片大小。
时间片过大
最后会变成:
时间片过小
导致:
频繁中断
频繁上下文切换
CPU大量时间用于切换因此不能简单认为:
“时间片越小响应越快,所以越好。”
题库专门考过时间片大小受响应要求、进程数量、系统处理能力、进程运行情况等因素影响。
二十二、多级反馈队列 MLFQ——一定要理解思想
这是现实意义很强的算法。
假设:
Q1:最高优先级,时间片2
Q2:较低优先级,时间片4
Q3:最低优先级,时间片8新进程首先:
→ Q1如果一个时间片没执行完:
Q1 → Q2再没完:
Q2 → Q3所以:
短进程
→ 很快在高优先级完成
长进程
→ 慢慢下降
I/O型进程
→ 经常只运行很短时间就阻塞
→ 通常保持较高优先级这就是为什么它能兼顾:
短作业
交互进程
I/O进程
长作业而且无需提前知道运行时间。
二十三、调度题怎么做?
看到一堆:
到达时间
运行时间
优先级
时间片不要凭感觉。
统一做:
时间轴
0────2────5────8────...每个调度点重新判断:
现在谁已经到达?
谁处于就绪态?
算法要求选谁?
是否允许抢占?
还剩多少运行时间?最后求:
再求:
这是处理调度综合题最稳定的方法。
二十四、调度、模式切换、上下文切换——极易混
三个概念必须分开。
调度
只是:
决定谁运行。
上下文切换
真正执行:
保存P1
↓
恢复P2
↓
CPU执行P2模式切换
只是:
用户态 ↔ 内核态例如:
P1用户态
↓
系统调用
P1内核态
↓
返回
P1用户态还是 P1。
因此:
而进程上下文切换必须在内核支持下完成。资料也专门区分了“调度是决策、切换是执行”。
二十五、同步与互斥——第 2 章最重要的理论基础
为什么需要同步?
因为并发意味着:
P1什么时候运行
P2什么时候运行具有不确定性。
如果两个进程都访问:
count++;它可能实际分解为:
读count
加1
写count两个进程一交叉,就可能出错。
二十六、临界资源与临界区
临界资源:
一段时间只允许一个进程使用的资源。
例如:
打印机
共享变量
共享链表访问临界资源的代码叫:
临界区。
典型结构:
进入区
临界区
退出区
剩余区二十七、同步和互斥的区别
互斥
本质:
不能一起干。
例如:
P1写文件
P2写文件同一时间只能一个。
属于竞争关系。
同步
本质:
必须按顺序干。
例如:
P1产生数据
↓
P2使用数据必须:
P1 → P2属于协作关系。
所以最好这样记:
互斥:谁先进去的问题
同步:谁先发生的问题二十八、临界区四大准则
资料给出四条。
核心意义可以压缩成:
| 原则 | 含义 |
|---|---|
| 空闲让进 | 没人在用就赶紧进去 |
| 忙则等待 | 有人在用别人不能进 |
| 有限等待 | 不能让人永远等 |
| 让权等待 | 等不到就交出 CPU |
注意:
前三条强调正确性;“让权等待”主要强调效率,并不是所有低级互斥算法都满足。
二十九、Peterson 算法
408软件互斥算法里最重要。
它结合两个东西:
flag[i]:我想不想进去
turn:如果两人都想进,让谁优先核心:
flag[i] = true;
turn = j;
while (flag[j] && turn == j);
临界区
flag[i] = false;理解一句话即可:
“我想进去,但如果你也想进,我先让你。”
若两个人同时谦让,由最后一次 turn 写入决定谁等,所以不会两个人一起进去,也不会两个人永远互等。
Peterson 满足:
空闲让进
忙则等待
有限等待但存在:
忙等待因此不满足让权等待。
资料将 Peterson 原理列为历年考点。
三十、硬件互斥:TS / Swap
本质都是解决一个问题:
“检查锁”和“加锁”必须一次完成。
否则:
P1:看到锁空闲
切换
P2:看到锁也空闲两者就都可能进入。
TestAndSet 由硬件保证原子性:
while (TestAndSet(&lock));获得成功才能进入临界区。
缺点仍然是:
忙等待三十一、信号量——整个第2章最重要内容之一
这里必须真正理解,而不是背代码。
记录型信号量:
S.value
等待队列P:
S.value--;
if (S.value < 0)
block();V:
S.value++;
if (S.value <= 0)
wakeup();于是:
P 操作
含义:
申请一个资源 / 等待一个条件。
V 操作
含义:
释放一个资源 / 通知一个事件完成。
三十二、信号量负数到底是什么意思?
这是很多人容易没真正理解的地方。
假设初始:
S = 3表示有 3 个资源。
执行三次 P:
3 → 2 → 1 → 0资源刚好被占完。
第四个进程 P:
0 → -1于是阻塞。
第五个:
-1 → -2又阻塞。
因此记录型信号量中:
可以理解为:
有进程正在等待。
其绝对值与等待情况相关。
三十三、互斥信号量为什么初值通常是 1?
因为临界资源只有一个:
semaphore mutex = 1;进程:
P(mutex);
临界区;
V(mutex);第一个进程:
1 → 0成功进入。
第二个:
0 → -1阻塞。
于是同一时刻最多一个进入。
三十四、同步信号量为什么通常初值是 0?
例如:
P1先执行A
P2才能执行B设:
semaphore S = 0;P1:
A;
V(S);P2:
P(S);
B;如果 P2 先运行:
P(S)
0 → -1直接阻塞。
等 P1:
A完成
V(S)再唤醒 P2。
因此同步题的核心口诀是:
谁提供条件,谁在完成后 V;谁需要条件,谁在动作前 P。
这是资料总结的 PV 解题核心思想。
三十五、PV 大题的通用解题方法
看到复杂题不要直接写 P/V。
先问:
第一步:有哪些进程?
例如:
生产者
消费者第二步:哪些关系是互斥?
例如:
共同修改缓冲区
→ mutex第三步:哪些关系是同步?
例如:
没有产品消费者不能取
→ full
没有空位置生产者不能放
→ empty第四步:定义信号量语义
mutex = 1; // 是否可进入缓冲区
empty = n; // 空缓冲区数量
full = 0; // 产品数量第五步:最后才写 P/V。
这样做比背模板稳定得多。
三十六、生产者—消费者问题——必须达到“闭眼会写”
容量:
定义:
semaphore mutex = 1;
semaphore empty = n;
semaphore full = 0;生产者:
生产产品;
P(empty);
P(mutex);
放入产品;
V(mutex);
V(full);消费者:
P(full);
P(mutex);
取出产品;
V(mutex);
V(empty);
消费产品;一定要理解三把“锁”的语义:
empty:还有没有位置?
full:有没有东西?
mutex:能不能进去操作缓冲区?三十七、一个极重要的死锁陷阱
生产者必须:
P(empty);
P(mutex);不能轻易写成:
P(mutex);
P(empty);假设缓冲区已满。
生产者先:
P(mutex)占住缓冲区互斥锁。
然后:
P(empty)因为没有空位而阻塞。
但是消费者想取东西必须先:
P(mutex)mutex 又在生产者手里。
于是:
生产者:
等消费者腾位置
消费者:
等生产者释放mutex死锁。
所以:
同步资源条件一般应先申请,互斥锁后申请。
这是PV综合题非常重要的审错方法。
三十八、读者—写者问题
关系是:
读者—读者:可以一起
写者—写者:互斥
读者—写者:互斥困难在于:
多个读者应该共同“占有”文件。
因此引入:
count记录读者数。
只有:
第一个读者负责锁住写者。
只有:
最后一个读者负责释放写者。
即:
if (count == 0)
P(rw);和:
if (count == 0)
V(rw);但是多个读者都会修改 count,所以还需要:
mutex保护 count。
这是读者写者题的本质:
资料还进一步区分了读者优先与通过额外信号量限制新读者进入的写者优先方案。
三十九、哲学家进餐
5 个哲学家:
P0 P1 P2 P3 P45 根筷子。
每人需要左右两根。
最危险的情况:
5人同时拿左筷子随后所有人:
等右筷子于是:
P0等P1
P1等P2
P2等P3
P3等P4
P4等P0形成环:
死锁。
解决思路本质不是“背代码”,而是:
破坏死锁四个必要条件之一。
例如:
最多允许4人同时拿筷子或者:
奇数先左后右
偶数先右后左都在破坏产生完整循环等待的可能。
四十、管程
管程可以理解为:
“把共享数据和操作这些数据的方法封装起来,并自动保证一次只有一个进程执行其中的过程。”
它解决了一个现实问题:
信号量代码分散:
这里P
那里V程序员很容易写错。
管程变成:
monitor Buffer
{
共享数据;
put();
get();
}外面只能调用它提供的方法。
条件不满足时使用:
condition.wait()条件满足后:
condition.signal()条件变量本身不像信号量那样表示“资源数量”,主要负责:
等待队列
+
阻塞/唤醒管程属于理解型和选择题知识点,重要度低于 PV 大题。
四十一、死锁——本章第四大重点
定义:
多个进程互相等待对方已经占有的资源,导致所有相关进程永久无法推进。
典型:
P1已有R1
想要R2
P2已有R2
想要R1于是:
P1 → 等P2
P2 → 等P1四十二、死锁四个必要条件——必须原话级掌握
必须同时成立:
| 条件 | 含义 |
|---|---|
| 互斥 | 一个资源一次只能一进程用 |
| 不可剥夺 | 已分配资源不能被强制夺走 |
| 请求并保持 | 拿着旧资源继续申请新资源 |
| 循环等待 | P1等P2,P2等P3……Pn等P1 |
记忆逻辑:
资源只能一个人用
+
拿到了别人不能抢
+
拿着一个还申请另一个
+
最后围成一个环
=
死锁资料强调:四个条件必须同时满足,破坏任意一个就不会发生死锁。
四十三、死锁和饥饿的区别
特别爱考。
死锁
大家互相等相关进程通常都阻塞。
饥饿
别人一直被选
我一直没被选例如 SJF:
长作业一直在就绪队列
短作业不断插队所以:
饥饿进程可以是就绪态。
而死锁进程是因为等待资源而无法推进。
四十四、死锁的三种处理思想
非常重要:
预防
避免
检测+解除千万不要混。
死锁预防
提前规定:
四个必要条件至少破坏一个。
属于比较死板的静态限制。
死锁避免
不一定破坏四条件。
而是:
每次分配前算一下,这次分完以后还有没有安全出路?
典型:
死锁检测
前面不严格限制。
让系统正常分配。
之后:
定期检查
↓
发现死锁
↓
解除四十五、安全状态——理解银行家算法之前必须搞懂
假设系统存在一种顺序:
P2
↓
P4
↓
P1
↓
P3按照这个顺序,每个进程都能拿到剩余资源、完成并释放资源。
这个顺序叫:
安全序列。
存在安全序列:
注意最重要的逻辑:
但:
不安全只是:
“已经无法保证未来一定不会死锁。”
资料专门强调了这个区别。
四十六、银行家算法——完整做题流程
这是必须练熟的算法。
四个核心数据结构:
Available
Max
Allocation
Need其中:
假设进程:
提出:
第一关:
超过自己声明的最大需求:
非法。
第二关:
当前资源不够:
等待。
如果两关都过:
试探性分配。
更新:
然后运行:
安全性算法。
四十七、安全性算法
令:
然后不断找某个尚未完成的进程:
如果找到,假装它执行完成。
它完成以后,会释放的是:
因此:
注意这里非常容易写错:
不是加
Need。
为什么?
因为 Need 是为了让它完成临时借给它的资源,它最终完成后释放的是它已经占有的全部资源。在安全性算法的等价计算中,新的可用资源更新为当前 Work 加该进程原先的 Allocation。
然后继续找下一个。
最终全部进程都能完成:
安全否则:
不安全资料给出了完整的银行家算法矩阵与安全序列例题,这类题一定要实际手算,而不是只看。
四十八、银行家算法最常见的四个错误
第一种是忘记先算:
第二种是只看:
就直接分配。
这是错的,还必须跑安全性算法。
第三种是安全算法更新 Work 时写错。
正确:
第四种是只找到一个可运行进程就宣布安全。
必须最终:
所有进程都能完成。
四十九、资源分配图与死锁检测
图中:
进程 → 资源表示:
请求。
资源 → 进程表示:
已分配。
特别重要:
有环不一定死锁。
如果每种资源只有一个实例:
但如果某类资源有多个实例:
有环只能说明有风险,不能直接断定死锁。
资料通过资源分配图简化给出的判断是:
若资源分配图最终不能完全简化,则系统存在死锁。
五十、死锁解除
已经死锁以后主要三种办法:
剥夺资源
撤销进程
进程回退本质都是:
强行破坏已经形成的循环等待关系。
这部分理解即可,计算量远小于银行家算法。
五十一、多处理机调度——理解即可
资料还讲了 SMP 下两个互相冲突的目标:
处理器亲和性
vs
负载平衡处理器亲和性:
一个进程最好继续在原 CPU 上跑,因为 Cache 中已有它的数据。
负载平衡:
又希望所有 CPU 都尽量有活干。
因此:
为了负载平衡
可能需要迁移进程
但迁移进程
又损失Cache亲和性这部分通常以选择题理解为主,不需要像单 CPU 调度算法那样大量计算。
五十二、整章最重要的知识网络
你最后应该把第 2 章在脑子里连接成这样:
程序并发
↓
进程
↓
PCB记录状态和现场
↓
就绪/运行/阻塞
↓
多个进程争CPU
↓
CPU调度
↓
进程不断交替执行
↓
共享数据出现竞争
↓
同步 + 互斥
↓
锁 / 信号量 / 管程
↓
生产者消费者
读者写者
哲学家问题
↓
资源申请不合理
↓
死锁
↓
四个必要条件
↓
预防 / 避免 / 检测解除
↓
银行家算法这比把几十个概念分别背下来重要得多。
五十三、408复习时建议掌握到什么程度
这一章真正需要达到“能做题”的核心只有四大块。
第一块是进程和线程。看到任意状态转换,要立即判断“它缺 CPU 还是缺事件”;看到进程和线程比较,要立即想到“进程管资源,线程管执行”,以及共享资源和私有现场。
第二块是调度算法。看到 FCFS、SJF/SRTF、HRRN、优先级、RR、多级反馈队列,要能自己画完整时间线并算:
第三块是整章最值得反复练的 PV 大题。不要背题,而要训练“进程 → 关系 → 信号量语义 → 初值 → P/V位置”的分析流程。资料明确指出信号量同步题在历年统考中频繁出现。
第四块是死锁和银行家算法。四个必要条件要秒答,预防/避免/检测不能混,银行家算法必须能手算安全序列。
如果把第 2 章的重要程度进一步压缩,可以记成:
其中前面三块不仅要“理解”,而且必须做到拿题能完整推出来。