简介:这份资料是汤小丹《计算机操作系统》教材的课后习题参考答案,面向高校计算机专业学生、考研复习者以及准备操作系统相关面试的开发者,用于课后巩固、期末复习与知识点自查。压缩包内共1个doc文档,约654KB,按章节整理,可直接检索查阅。内容覆盖操作系统的主要目标与作用、计算机资源抽象、多道批处理与分时系统的形成动力、实时系统及硬实时与软实时任务的区分,并系统梳理了OS的并发性、共享性、虚拟性和异步性四大特征,以及处理机管理、内存管理、设备管理和文件管理的主要功能与任务。每道题均给出条理清晰的解答,如脱机I/O与联机I/O的区别、分时系统与实时系统在交互性、及时性、可靠性上的比较、微内核OS的客户/服务器模式与优点等,便于读者对照教材逐章核对答案、理解概念脉络。目前已有3900人学习下载,适合需要系统刷题与查漏补缺的操作系统学习者。
1. 汤小丹版操作系统课后题:为什么“对答案”反而容易挂科
很多人拿到《计算机操作系统》汤小丹版的课后习题,第一反应是找一份“课后答案”对着抄。我当年也这么干过,结果期末卷子上那道“银行家算法求安全序列”的题,我明明背过答案,却因为题目把 Available 和 Need 矩阵换了个顺序,直接算崩。后来才想明白:这门课的课后题不是用来“对答案”的,而是用来暴露你对进程同步、内存分配、页面置换这些机制的理解漏洞的。汤小丹这本教材的习题有个特点——计算量大、状态转移多、边界条件刁钻,光看答案根本不知道中间那步为什么这么跳。所以这篇笔记不打算给你一份“标准答案合集”,而是把课后题里最高频的几类题型拆成可复现的解题路径:银行家算法怎么手算不出错、PV 操作怎么从语义反推代码、页面置换缺页率怎么列表格不丢分。适合正在学这门课、准备考研复试、或者带学生做实验的从业者。你照着下面的步骤走一遍,比背十份答案都管用。
2. 银行家算法课后题:从“背答案”到“手推安全序列”
2.1 为什么汤小丹的银行家算法题总让人翻车
汤小丹教材里银行家算法的课后题,通常给一张表:Process、Allocation、Max、Available,然后让你求 Need 矩阵、找安全序列、判断某次请求能否分配。很多人翻车不是因为不会算,而是因为算到一半忘了更新 Available。我见过最典型的血泪经验:一个同学把 P1 释放后的资源加回 Available,接着算 P2 时却用了旧的 Available,整条安全序列全错。银行家算法的本质是一个状态搜索问题——每选一个进程,系统状态就变一次,你必须像调试器一样跟踪每一步的 Work 和 Finish。教材课后题之所以反复考,就是因为它能逼你养成“每步更新、每步记录”的习惯。下面我给出一个通用的手算模板,你拿任何一道汤小丹的课后题套进去都不会乱。
2.2 手算安全序列的固定五步法(附 Python 验证脚本)
先看一道典型题:5 个进程 P0~P4,3 类资源 A/B/C。题目给出 Allocation 和 Max,Available 初始为 (3,3,2)。要求判断是否存在安全序列。我一般会按下面五步走,每一步都在草稿纸上写清楚。
第一步:算 Need 矩阵。Need = Max - Allocation,逐行相减,负数说明题目数据有误。
第二步:初始化 Work = Available,Finish 全为 false。
第三步:找满足 Need[i] ≤ Work 的进程。注意是每个分量都 ≤,不是总和。
第四步:假设该进程完成,Work = Work + Allocation[i],Finish[i] = true。
第五步:重复第三步,直到所有 Finish 为 true(安全)或找不到可执行进程(不安全)。
下面这段 Python 脚本可以直接验证你的手算结果,把题目数据填进去就能跑:
# 银行家算法安全序列验证脚本 # 适用:汤小丹《计算机操作系统》课后习题典型数据 def is_safe(available, max_mat, alloc): n = len(max_mat) # 进程数 m = len(available) # 资源类数 need = [[max_mat[i][j] - alloc[i][j] for j in range(m)] for i in range(n)] work = available[:] finish = [False] * n safe_seq = [] while len(safe_seq) < n: found = False for i in range(n): if not finish[i] and all(need[i][j] <= work[j] for j in range(m)): # 模拟进程 i 执行完成,释放其已占资源 for j in range(m): work[j] += alloc[i][j] finish[i] = True safe_seq.append(f"P{i}") found = True break if not found: return False, [] # 存在死锁,无安全序列 return True, safe_seq # 以教材常见数据为例 available = [3, 3, 2] max_mat = [[7,5,3],[3,2,2],[9,0,2],[2,2,2],[4,3,3]] alloc = [[0,1,0],[2,0,0],[3,0,2],[2,1,1],[0,0,2]] ok, seq = is_safe(available, max_mat, alloc) print("安全:" , ok, "序列:", seq)逻辑说明:脚本严格按五步法实现,need矩阵自动计算,work每轮更新,finish标记已完成进程。参数说明:available是初始可用资源向量;max_mat是最大需求矩阵;alloc是已分配矩阵。运行结果会输出安全序列,比如P1 -> P3 -> P4 -> P0 -> P2。如果你手算的序列和它不一样但都安全,也是对的——安全序列不唯一,这是汤小丹课后题常设的陷阱,别因为对不上“参考答案”就怀疑自己。
2.3 请求分配判断题:多问一句“然后呢”
课后题第二类考法是:某进程发出 Request 向量,问能否分配。很多人只检查Request ≤ Need和Request ≤ Available就下结论,漏了最关键的一步——试分配后系统是否仍安全。正确做法是:先假装分配,修改 Available、Allocation、Need,然后重新跑一遍安全序列检测。如果安全才真正分配,否则回滚。我习惯在草稿纸边上画一个“试分配区”,和原状态分开写,避免改乱。这个习惯在考场上救过我至少两次。
3. PV 操作课后题:从语义反推代码,而不是背代码
3.1 汤小丹 PV 题的出题套路
汤小丹教材的 PV 操作课后题,几乎都围绕生产者-消费者、读者-写者、哲学家进餐三个模型变体。题目通常给一段自然语言描述,让你用 P、V 原语写出同步算法。很多人背了标准答案,但题目一改条件就懵——比如把缓冲区从 1 个改成 n 个,或者把“读者优先”改成“写者优先”。根本原因是你背的是代码,不是信号量的语义。PV 操作的核心就一句话:P 表示申请资源,V 表示释放资源;信号量的值代表当前可用资源数。你只要把题目里的“等待条件”翻译成 P,“完成后的通知”翻译成 V,代码自然就出来了。
3.2 用“资源视角”重写生产者-消费者
以最常见的单缓冲区生产者-消费者为例。题目描述:生产者往缓冲区放数据,消费者取数据,缓冲区满时生产者等,空时消费者等。用资源视角拆解:
- 缓冲区空位 = 一种资源,初始有 1 个,生产者需要 P 它,消费者取走后 V 它。
- 缓冲区已有数据 = 另一种资源,初始 0 个,消费者需要 P 它,生产者放入后 V 它。
- 互斥访问缓冲区 = 一个互斥信号量 mutex,初始 1。
对应代码:
// 单缓冲区生产者-消费者(汤小丹教材典型模型) semaphore empty = 1; // 空位数 semaphore full = 0; // 数据数 semaphore mutex = 1; // 缓冲区互斥锁 // 生产者 void producer() { while (1) { produce_item(); P(empty); // 申请一个空位,满则阻塞 P(mutex); // 进入临界区 put_item(); V(mutex); // 离开临界区 V(full); // 通知消费者:多了一个数据 } } // 消费者 void consumer() { while (1) { P(full); // 申请一个数据,空则阻塞 P(mutex); get_item(); V(mutex); V(empty); // 通知生产者:多了一个空位 consume_item(); } }逻辑说明:P(empty)必须在P(mutex)之前,否则可能死锁——这是汤小丹课后题最爱考的“顺序陷阱”。参数说明:empty初值为缓冲区容量,full初值为 0,mutex初值为 1。如果题目改成 n 个缓冲区,只需把empty初值改成 n,其余不变。你看,根本不用背新代码。
3.3 读者-写者:加一个计数器就变“写者优先”
读者-写者问题是汤小丹课后题的高频变体。标准“读者优先”版本里,读者只要有一个在读,写者就得等。实现关键是第一个读者负责 P 写锁,最后一个读者负责 V 写锁,中间用一个count计数器。代码骨架:
semaphore rmutex = 1; // 保护 count semaphore wmutex = 1; // 写锁 int count = 0; void reader() { P(rmutex); if (count == 0) P(wmutex); // 第一个读者锁住写者 count++; V(rmutex); read(); P(rmutex); count--; if (count == 0) V(wmutex); // 最后一个读者释放写者 V(rmutex); } void writer() { P(wmutex); write(); V(wmutex); }如果题目要求“写者优先”,常见做法是再加一个信号量w,读者和写者都先 P(w),写者优先获得。这个变体在汤小丹课后题里出现过多次,你只要抓住“谁先 P 谁优先”的原则就能推出来。
4. 页面置换课后题:缺页率表格怎么列才不丢分
4.1 OPT、FIFO、LRU 的手算差异
汤小丹教材内存管理章节的课后题,几乎必考页面置换算法:给一个页面引用串,比如7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1,物理块数为 3,让你分别用 OPT、FIFO、LRU 求缺页次数和缺页率。很多人算 FIFO 时忘了“Belady 异常”,算 LRU 时把最近最久未使用和最近未使用搞混。我一般会画一张表:每一列是一个引用页,每一行是一个物理块,表头标注“是否缺页”。OPT 看未来,FIFO 看进入时间,LRU 看最近访问时间。下面用 Python 模拟三种算法,你可以拿它验证手算结果:
# 页面置换算法模拟:OPT / FIFO / LRU def page_faults(ref_str, frames, algo): mem = [] faults = 0 for i, page in enumerate(ref_str): if page in mem: if algo == 'LRU': mem.remove(page) mem.append(page) # 最近使用移到末尾 continue faults += 1 if len(mem) < frames: mem.append(page) else: if algo == 'OPT': # 找未来最长时间不再使用的页 farthest, idx = -1, -1 for j, m in enumerate(mem): try: nxt = ref_str[i+1:].index(m) except ValueError: nxt = float('inf') if nxt > farthest: farthest, idx = nxt, j mem[idx] = page elif algo == 'FIFO': mem.pop(0) mem.append(page) elif algo == 'LRU': mem.pop(0) # 最久未使用在头部 mem.append(page) return faults ref = [7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1] for a in ['OPT','FIFO','LRU']: f = page_faults(ref, 3, a) print(f"{a}: 缺页 {f} 次, 缺页率 {f/len(ref):.2%}")逻辑说明:mem列表模拟物理块,FIFO 用pop(0)淘汰最早进入的,LRU 用pop(0)淘汰最久未使用的(因为每次访问命中都会把页移到末尾)。参数说明:ref_str是页面引用串,frames是物理块数,algo取OPT/FIFO/LRU。运行后你会看到 OPT 缺页最少,FIFO 可能出现 Belady 异常(增加物理块反而缺页更多),LRU 介于两者之间。手算时建议用铅笔,因为 OPT 需要反复看未来串,容易看花眼。
4.2 缺页率计算的两个边界坑
第一个坑:引用串第一个页一定缺页,但很多人算缺页率时把总访问次数算错,比如把重复引用漏算。第二个坑:物理块数初始为空,前 frames 次访问即使页面不同也一定缺页,别以为“内存里没有但之前出现过”就不算缺页。汤小丹课后题经常在引用串里埋重复页,就是考你这两点。我习惯在表格最下面单独写一行“累计缺页数”,每列更新一次,最后除以总列数,这样不会乱。
5. 避坑与排查:课后题里最容易翻车的 4 个点
5.1 现象:银行家算法安全序列和参考答案不一样
原因:安全序列不唯一,只要每一步都满足 Need ≤ Work,就是合法序列。解决:用第 2 章的 Python 脚本验证你的序列,只要脚本判定安全,就说明你的答案正确,不要强行改成参考答案的顺序。
5.2 现象:PV 操作代码运行结果死锁
原因:P 操作顺序反了。比如生产者先 P(mutex) 再 P(empty),当缓冲区满时,生产者持有 mutex 等待 empty,消费者无法进入临界区释放 empty,死锁。解决:永远先 P 资源信号量(empty/full),再 P 互斥信号量(mutex)。这是汤小丹教材反复强调的“资源在前,互斥在后”。
5.3 现象:LRU 和 FIFO 算出来缺页次数一样
原因:引用串太短或物理块太多,两种算法恰好表现一致。解决:换一个引用串验证,比如1,2,3,4,1,2,5,1,2,3,4,5,物理块 3,FIFO 缺页 9 次,LRU 缺页 10 次,差异就出来了。别因为一次巧合就怀疑自己算错。
5.4 现象:OPT 算法手算时把“未来最远”看成“未来最近”
原因:审题不清。OPT 淘汰的是未来最长时间不再访问的页,不是最近要访问的页。解决:在草稿纸上把未来引用串抄一遍,对每个在内存中的页标注“下一次出现的位置”,选位置最靠后(或不再出现)的淘汰。这个动作慢但准,考场上别省。
6. 用“错题反推”把课后题变成自己的知识图谱
最后一章说一个我用了很多年的技巧:不要按章节顺序刷汤小丹的课后题,而是按“错题类型”反推知识漏洞。具体做法是,准备一个表格,三列:题目编号、考的知识点、我错在哪。每做错一道,就填一行。坚持两周,你会发现自己的错误集中在某几个机制上——比如“信号量初值设错”或“页面置换表漏列”。然后针对这些机制,回到教材对应小节重读,再用第 2、3、4 章的脚本验证。下面是我当年整理的一张示例表:
| 题目来源 | 知识点 | 错误现象 | 修正动作 |
|---|---|---|---|
| 第 3 章 习题 12 | 银行家算法 | Available 未更新 | 用脚本逐步打印 Work |
| 第 4 章 习题 8 | PV 操作 | P 顺序反了 | 重画资源视角图 |
| 第 5 章 习题 6 | LRU | 命中未移末尾 | 手算时用箭头标最近使用 |
| 第 6 章 习题 3 | 页面置换 | 缺页率算错 | 表格加累计行 |
这个习惯的好处是,你不再依赖“课后答案”的对错,而是依赖自己的验证脚本和错题记录。汤小丹这本教材的课后题质量很高,但答案版本鱼龙混杂,我见过不少把 FIFO 和 LRU 结果写反的“参考答案”。与其赌答案对不对,不如自己跑一遍脚本。我现在的习惯是:每学完一章,先手算两道典型题,再用脚本验证,最后把错题填进表格。希望帮到你。
本文还有配套的精品资源,点击获取