news 2026/11/5 19:25:22

操作系统第八版习题答案:从静态文档到可运行代码的解题框架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
操作系统第八版习题答案:从静态文档到可运行代码的解题框架

简介:这份文档面向正在学习《操作系统精髓与设计原理》第八版的高校学生与考研备考者,针对课后习题缺少参考答案、进程与并发概念难以自检的痛点,提供系统化的解题参考。压缩包内仅含1个docx文件,约904KB,按章节组织答案,便于对照教材逐题查阅。内容覆盖进程管理、进程调度、资源管理与并发编程四大模块,例如习题3.1以状态转换图梳理就绪态、运行态、阻塞态与非常驻态之间的六类转换事件,3.2结合时刻5至48的读写磁盘与中断事件,推演各进程在时刻22、37、47的状态,3.5则给出兼顾优先级与交换开销的中间调度策略,5.3与5.4通过共享变量x和tally的交替执行,分析伪并行下的输出顺序与取值范围。目前已有7513人学习下载,适合作为章节复习、作业核对与考前查漏补缺的参考材料。

1. 操作系统第八版习题答案:从一份文档到一套可复现的操作系统解题框架

很多人第一次拿到「操作系统第八版习题答案.docx」这类文档时,第一反应是把它当成背诵材料,考前翻一翻,考完就丢。但如果你真的做过操作系统相关的工程,会发现这份文档里藏着的不是答案,而是一套解题框架——进程调度怎么算周转时间、页面置换怎么模拟缺页率、死锁检测怎么建资源分配图,这些东西在真实系统调优里天天用。问题在于,大部分答案文档只给结果不给过程,你抄完还是不会。我见过太多人对着答案点头,合上文档就懵。所以这篇笔记不打算复述某份答案,而是把「操作系统第八版习题答案」这个标题拆开,讲清楚怎么把一份静态文档变成可运行、可验证、可迁移的解题能力。适合正在学操作系统、准备考试、或者想补底层功底的后端和嵌入式从业者。

2. 先搞清楚习题答案里到底在考什么:四类题型与对应解法

2.1 计算类题型:调度、置换、磁盘寻道

操作系统习题里占比最大的是计算题。进程调度算平均周转时间和带权周转时间,页面置换算缺页次数和缺页率,磁盘调度算总寻道长度。这三类题的共同点是:公式不难,但手算容易错,尤其是题目给了十几条进程或十几个页面引用串的时候。

我一般建议先把公式写成代码,再用代码去跑题目数据。比如先来先服务、短作业优先、时间片轮转这三种调度算法,用 Python 写出来不到五十行,但能帮你验证手算结果。页面置换的 OPT、FIFO、LRU 也一样,尤其是 LRU,手算容易把最近使用顺序搞混,代码跑一遍就清楚了。

这里有个关键认知:习题答案给的是某一组输入的输出,但考试和实际工作换一组输入你就得重新算。所以真正要掌握的是算法逻辑,不是那一组数字。

2.2 分析类题型:死锁、同步、内存分配

分析类题目通常给一个场景,问你「是否会发生死锁」「信号量初值该设多少」「首次适应和最佳适应哪个更省空间」。这类题没有唯一数值答案,但有标准分析路径。

死锁分析的标准路径是:画资源分配图,找环路,判断环路是否涉及单个资源的多重请求。如果每个资源只有一个实例,有环就有死锁;如果资源有多个实例,有环不一定死锁,还要看能否找到安全序列。这个判断逻辑用代码实现就是银行家算法的安全性检查。

信号量题的标准路径是:先确定有几个同步点和互斥点,每个点需要一个信号量,然后确定初值。互斥信号量初值一般为 1,同步信号量初值看资源初始可用数量。很多人错在把互斥和同步混在一起,导致 P、V 操作顺序写反。

2.3 设计类题型:文件系统、I/O 调度、虚拟内存

设计类题目要求你给出一个方案,比如「设计一个支持大文件的索引结构」「设计一个磁盘调度策略兼顾公平和吞吐」。这类题在习题答案里通常只给一个参考方案,但实际可以有多种正确解法。

我的做法是先确定约束条件:文件最大多大、磁盘块多大、内存有多少、访问模式是顺序还是随机。约束定了,方案基本就定了。比如多级索引,如果文件最大 1GB,块大小 4KB,那么直接索引加一级间接加二级间接就够,不需要三级。习题答案如果给了三级,你要看它的约束是不是和你一样。

2.4 概念类题型:对比、简述、判断

概念题看似简单,但最容易丢分。比如「进程和线程的区别」「分页和分段的区别」「中断和异常的区别」,答案文档里通常列了五六条,但你真到面试或工作中被问到,能说清楚三条核心区别就不错了。

我建议把概念题整理成对比表,每行一个维度,每列一个对象。这样复习时一眼能看出差异,答题时也不会漏点。下面这张表是我自己整理的分页与分段对比,你可以照着这个格式整理其他概念。

对比维度分页分段
划分依据物理块大小固定逻辑模块大小可变
地址空间一维二维
碎片类型内部碎片外部碎片
对用户可见不可见可见
共享与保护不易实现容易实现

3. 把答案文档变成可运行代码:三类核心算法的 Python 实现

3.1 进程调度算法:FCFS、SJF、RR 的代码实现

先看调度算法。下面这段代码实现了 FCFS、非抢占 SJF 和时间片轮转三种调度,输入是进程列表,每个进程包含到达时间和服务时间,输出是平均周转时间和平均带权周转时间。

# 进程调度算法实现:FCFS、SJF、RR # 输入:processes = [(pid, arrive, burst), ...] # 输出:平均周转时间、平均带权周转时间 def fcfs(processes): # 先来先服务:按到达时间排序,依次执行 procs = sorted(processes, key=lambda x: x[1]) time = 0 total_turnaround = 0 total_weighted = 0 for pid, arrive, burst in procs: if time < arrive: time = arrive # CPU 空闲等待 time += burst turnaround = time - arrive total_turnaround += turnaround total_weighted += turnaround / burst n = len(procs) return total_turnaround / n, total_weighted / n def sjf_nonpreemptive(processes): # 非抢占短作业优先:每次从已到达进程里选服务时间最短的 procs = list(processes) time = 0 total_turnaround = 0 total_weighted = 0 completed = 0 n = len(procs) while completed < n: # 筛选已到达且未完成的进程 ready = [p for p in procs if p[1] <= time] if not ready: time += 1 # 没有就绪进程,时间推进 continue # 选服务时间最短的 pid, arrive, burst = min(ready, key=lambda x: x[2]) time += burst turnaround = time - arrive total_turnaround += turnaround total_weighted += turnaround / burst procs.remove((pid, arrive, burst)) completed += 1 return total_turnaround / n, total_weighted / n def rr(processes, quantum): # 时间片轮转:每个进程执行一个时间片,未完成则排到队尾 from collections import deque procs = sorted(processes, key=lambda x: x[1]) queue = deque() time = 0 idx = 0 n = len(procs) remaining = {p[0]: p[2] for p in procs} finish_time = {} while queue or idx < n: # 把到达时间小于等于当前时间的进程加入队列 while idx < n and procs[idx][1] <= time: queue.append(procs[idx]) idx += 1 if not queue: time = procs[idx][1] continue pid, arrive, burst = queue.popleft() run = min(quantum, remaining[pid]) time += run remaining[pid] -= run if remaining[pid] == 0: finish_time[pid] = time else: # 未完成,重新加入队列(注意要放在新到达进程后面) queue.append((pid, arrive, burst)) total_turnaround = 0 total_weighted = 0 for pid, arrive, burst in procs: turnaround = finish_time[pid] - arrive total_turnaround += turnaround total_weighted += turnaround / burst return total_turnaround / n, total_weighted / n # 测试数据 processes = [ (1, 0, 7), (2, 2, 4), (3, 4, 1), (4, 5, 4), ] print("FCFS:", fcfs(processes)) print("SJF:", sjf_nonpreemptive(processes)) print("RR(q=2):", rr(processes, 2))

这段代码的关键点有三个。第一,FCFS 里要处理 CPU 空闲等待的情况,如果当前时间小于进程到达时间,时间要跳到到达时间。第二,SJF 非抢占版本每次只从已到达进程里选,不能选还没到的。第三,RR 里未完成的进程重新入队时,要放在当前已到达但还没入队的进程后面,否则会打乱轮转顺序。

参数方面,时间片 quantum 的选择很关键。时间片太大就退化成 FCFS,太小则上下文切换开销占比高。一般取略大于一次上下文切换时间的值,习题里通常给固定值,实际系统中常见范围是 10ms 到 100ms。

3.2 页面置换算法:OPT、FIFO、LRU 的模拟与缺页率对比

页面置换的代码比调度稍短,但细节更多。下面实现 OPT、FIFO、LRU 三种算法,输入是页面引用串和物理块数,输出缺页次数和缺页率。

# 页面置换算法:OPT、FIFO、LRU # 输入:refs = [页面号序列], frames = 物理块数 # 输出:缺页次数、缺页率 def opt(refs, frames): # 最佳置换:淘汰未来最长时间不被访问的页面 memory = [] faults = 0 for i, page in enumerate(refs): if page in memory: continue faults += 1 if len(memory) < frames: memory.append(page) else: # 找未来最远才被访问的页面 farthest = -1 victim = None for p in memory: # 找 p 在 i 之后第一次出现的位置 try: next_use = refs.index(p, i + 1) except ValueError: next_use = float('inf') # 之后不再使用 if next_use > farthest: farthest = next_use victim = p memory.remove(victim) memory.append(page) return faults, faults / len(refs) def fifo(refs, frames): # 先进先出:淘汰最早进入内存的页面 from collections import deque memory = deque() faults = 0 for page in refs: if page in memory: continue faults += 1 if len(memory) < frames: memory.append(page) else: memory.popleft() memory.append(page) return faults, faults / len(refs) def lru(refs, frames): # 最近最久未使用:淘汰最长时间未被访问的页面 memory = [] faults = 0 for page in refs: if page in memory: memory.remove(page) memory.append(page) # 移到最近使用位置 continue faults += 1 if len(memory) < frames: memory.append(page) else: memory.pop(0) # 淘汰最久未使用的 memory.append(page) return faults, faults / len(refs) # 测试:经典引用串 refs = [7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1] for name, func in [("OPT", opt), ("FIFO", fifo), ("LRU", lru)]: faults, rate = func(refs, 3) print(f"{name}: 缺页 {faults} 次, 缺页率 {rate:.2%}")

OPT 的实现要注意 refs.index(p, i+1) 这个用法,它在 i+1 之后找 p 第一次出现的位置,找不到就抛 ValueError,我们把它当作无穷远。FIFO 用队列实现最自然,但要注意 Python 的 deque 和 list 在删除操作上的性能差异。LRU 用 list 模拟,每次访问把页面移到末尾,淘汰时取头部,逻辑清晰但时间复杂度是 O(n),实际系统中会用哈希表加双向链表做到 O(1)。

跑这组经典数据,OPT 缺页 9 次,FIFO 缺页 15 次,LRU 缺页 12 次。你会发现 FIFO 出现了 Belady 异常——物理块增多缺页反而可能增加,而 LRU 和 OPT 不会。这个现象在习题里经常考,代码跑一遍比背结论印象深得多。

3.3 银行家算法:安全性检查与死锁避免

银行家算法是死锁避免的核心,习题里通常给 Allocation、Max、Available 三个矩阵,问是否存在安全序列。下面代码实现了安全性检查。

# 银行家算法安全性检查 # 输入:available 可用资源向量, max_need 最大需求矩阵, # allocation 已分配矩阵 # 输出:是否存在安全序列,以及该序列 def banker_safety(available, max_need, allocation): n = len(allocation) # 进程数 m = len(available) # 资源种类数 # 计算需求矩阵 Need = Max - Allocation need = [[max_need[i][j] - allocation[i][j] for j in range(m)] for i in range(n)] work = available[:] # 工作向量,初始等于 Available finish = [False] * n safe_seq = [] while len(safe_seq) < n: found = False for i in range(n): if finish[i]: continue # 检查 Need[i] 是否小于等于 Work if all(need[i][j] <= work[j] for j in range(m)): # 进程 i 可以执行完成,释放资源 for j in range(m): work[j] += allocation[i][j] finish[i] = True safe_seq.append(i) found = True break if not found: # 找不到可执行进程,不安全 return False, [] return True, safe_seq # 测试数据 available = [3, 3, 2] max_need = [ [7, 5, 3], [3, 2, 2], [9, 0, 2], [2, 2, 2], [4, 3, 3], ] allocation = [ [0, 1, 0], [2, 0, 0], [3, 0, 2], [2, 1, 1], [0, 0, 2], ] safe, seq = banker_safety(available, max_need, allocation) print("安全:" , safe, "序列:", seq)

这段代码的核心是 Work 向量的更新:进程执行完成后,已分配的资源要归还给 Work。注意 Need 矩阵是 Max 减 Allocation,不是单独给的。习题里有时直接给 Need,有时给 Max 和 Allocation,你要会换算。

参数方面,Available 是系统当前可用资源,Allocation 是已分配给各进程的资源,Max 是各进程声明的最大需求。安全性检查的时间复杂度是 O(n²m),进程数和资源种类数都不大时完全够用。

4. 避坑与排查:习题答案里最容易翻车的五个地方

4.1 周转时间算错:到达时间和服务时间的顺序搞反

现象:算出来的平均周转时间比答案大很多或小很多。原因:周转时间是完成时间减到达时间,不是服务时间减到达时间。很多人把服务时间当成完成时间,或者忘记加等待时间。解决:画甘特图,把每个进程的开始、完成时间标出来,周转时间等于完成减到达,带权周转时间等于周转除以服务。

4.2 缺页率算错:引用串长度和缺页次数对不上

现象:缺页率超过 100% 或者明显偏低。原因:缺页率是缺页次数除以总引用次数,不是除以物理块数。另外 OPT 算法里,如果页面在内存中但未来不再使用,它的下次使用位置是无穷大,应该优先淘汰。解决:先确认引用串长度,再数缺页次数,两者相除。OPT 的淘汰逻辑用代码跑一遍,别手算。

4.3 信号量初值设错:互斥和同步混在一起

现象:P、V 操作顺序写反,或者信号量初值设成 0 导致进程永远阻塞。原因:互斥信号量初值必须是 1,表示资源可用;同步信号量初值看资源初始数量,如果生产者要先生产消费者才能消费,同步信号量初值设 0。解决:先画同步点,每个同步点一个信号量,互斥单独一个信号量。P 操作顺序是先同步后互斥,V 操作顺序是先互斥后同步。

4.4 银行家算法漏判:Need 矩阵算错或 Work 更新遗漏

现象:明明有安全序列却判成不安全。原因:Need 矩阵应该是 Max 减 Allocation,不是直接抄题目给的某个矩阵。另外进程执行完成后,Work 要加上该进程的 Allocation,不是加上 Need。解决:把三个矩阵列成表格,逐行核对。Work 更新时只加 Allocation,不加 Max。

4.5 磁盘调度算错:寻道长度是相邻访问的差值绝对值

现象:总寻道长度算成最大磁道减最小磁道。原因:寻道长度是磁头从当前磁道移动到下一个目标磁道的距离,是相邻两个访问磁道号的差的绝对值,不是总范围。解决:把访问序列按算法排序,然后逐对计算差值绝对值再求和。SCAN 和 C-SCAN 要注意磁头移动方向,到达端点后是否反向。

5. 从习题到实战:用一套验证脚本把答案变成可迁移能力

5.1 建立自己的验证脚本库

习题答案最大的问题是不可验证。你抄完答案,不知道自己算的对不对。我的做法是给每类题型写一个验证脚本,输入题目数据,输出结果,然后和答案对比。上面三节给的调度、置换、银行家算法代码就是基础版本,你可以把它们存成一个 os_solver.py,以后遇到同类题直接调用。

更进一步,你可以给每个函数加上参数校验和边界测试。比如调度算法里,如果进程到达时间都是 0,SJF 和 FCFS 的结果应该一样;如果时间片设得比所有进程服务时间都大,RR 应该退化成 FCFS。这些边界条件能帮你确认代码逻辑是否正确。

5.2 用随机数据做交叉验证

手算容易错,但代码也可能有 bug。交叉验证的方法是:用随机生成的数据跑两种不同实现,看结果是否一致。比如 LRU 可以用 list 实现,也可以用 OrderedDict 实现,两者结果应该相同。如果不同,说明至少有一个实现有问题。

下面这段代码生成随机进程数据,分别用两种方式实现 LRU,对比缺页次数。

# LRU 交叉验证:list 实现 vs OrderedDict 实现 import random from collections import OrderedDict def lru_list(refs, frames): memory = [] faults = 0 for page in refs: if page in memory: memory.remove(page) memory.append(page) else: faults += 1 if len(memory) >= frames: memory.pop(0) memory.append(page) return faults def lru_ordered(refs, frames): memory = OrderedDict() faults = 0 for page in refs: if page in memory: memory.move_to_end(page) else: faults += 1 if len(memory) >= frames: memory.popitem(last=False) memory[page] = True return faults # 随机测试 100 组 for _ in range(100): refs = [random.randint(0, 9) for _ in range(50)] frames = random.randint(1, 5) f1 = lru_list(refs, frames) f2 = lru_ordered(refs, frames) if f1 != f2: print("不一致:", refs, frames, f1, f2) break else: print("100 组随机测试全部一致")

这段代码跑完如果输出「100 组随机测试全部一致」,说明两种实现逻辑等价。如果中间有不一致,打印出来的那组数据就是你的排查线索。

5.3 把习题结论映射到真实系统参数

习题里的参数往往是简化过的,比如页面大小固定 4KB,物理块数固定 3 个,时间片固定 2ms。真实系统里这些参数是动态的。Linux 的页面大小通常是 4KB,但可以配置为大页 2MB 或 1GB;物理内存的页面帧数量取决于可用内存;时间片在 CFS 调度器里不是固定值,而是根据进程优先级和负载动态计算。

我的习惯是:做完一道习题,去查一下真实系统里对应的参数范围。比如做完页面置换,去查 Linux 的页回收策略;做完调度,去查 CFS 的 vruntime 计算方式。这样习题就不是孤立的,而是真实系统的一个简化模型。

5.4 一个具体技巧:用甘特图验证调度结果

调度类习题最容易错的是周转时间。我的验证技巧是画甘特图:横轴时间,纵轴进程,每个进程一个条形,条形长度等于服务时间,条形起点是开始执行时间。画完图,每个进程的完成时间一目了然,周转时间就是完成时间减到达时间。

手画太慢,可以用 matplotlib 画。下面这段代码把 FCFS 的调度结果画成甘特图。

# 用 matplotlib 画 FCFS 调度甘特图 import matplotlib.pyplot as plt def fcfs_gantt(processes): procs = sorted(processes, key=lambda x: x[1]) fig, ax = plt.subplots() time = 0 for pid, arrive, burst in procs: if time < arrive: time = arrive ax.barh(pid, burst, left=time, height=0.5) ax.text(time + burst / 2, pid, f"P{pid}", ha='center', va='center') time += burst ax.set_xlabel("时间") ax.set_ylabel("进程") ax.set_title("FCFS 调度甘特图") plt.show() processes = [(1, 0, 7), (2, 2, 4), (3, 4, 1), (4, 5, 4)] fcfs_gantt(processes)

画出来之后,每个进程的开始和结束时间清清楚楚,周转时间直接读图就行。这个技巧我用了很多年,比反复手算靠谱得多。

5.5 最后的习惯:每道题都问一句「如果参数变了会怎样」

习题答案给的是特定输入下的输出。真正要掌握的是:如果到达时间变了、时间片变了、物理块数变了,结果会怎么变。我的习惯是每做完一道题,改一个参数再跑一遍,看结果的变化趋势。比如时间片从 2ms 改成 4ms,RR 的平均周转时间是变大还是变小;物理块数从 3 增加到 4,FIFO 的缺页次数是增加还是减少。

这个习惯坚持下来,你会发现习题答案不再是背诵材料,而是一个可以反复实验的平台。操作系统这门课的精髓不在答案本身,而在答案背后的算法逻辑和参数敏感性。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 19:25:03

激光焊缝跟踪系统:开启机器人具身智能之路

激光焊缝跟踪系统它不仅仅是一个提高焊接精度和效率的工业工具&#xff0c;更是赋予工业机器人初步的、与物理环境进行实时动态交互能力的关键技术。它实现了基础的“感知-行动”闭环&#xff0c;解决了机器人适应环境不确定性的核心难题&#xff0c;完美体现了具身智能的核心思…

作者头像 李华
网站建设 2026/11/5 19:25:22

大模型应用后端设计:上下文管理、缓存、降级策略的工程方案总结

上一篇文章讨论了 Agent 的核心组件——RAG、向量库、Function Calling 与 Skill 体系。但把这些组件拼起来之后&#xff0c;更棘手的问题浮出水面&#xff1a;多轮对话怎么管&#xff1f;重复请求怎么省&#xff1f;上游挂了怎么办&#xff1f; 这三个问题对应的正是大模型后端…

作者头像 李华
网站建设 2026/11/5 19:25:22

幽默代码注释与终端Banner:程序员整活的工程边界与实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/4 23:55:10

Flutter瀑布流与GridView深度解析:性能、布局与渲染原理

1. 项目概述&#xff1a;为什么一个看似简单的 GridView 示例值得花一整篇深度拆解&#xff1f;Flutter 里写个网格列表&#xff0c;不就是套个GridView.builder、设个crossAxisCount就完事了&#xff1f;我最初也这么想。直到在某跨平台系统重构中&#xff0c;用默认GridView实…

作者头像 李华
网站建设 2026/10/24 19:18:52

【荣登榜单】感恩支持,感谢一路相伴!2025年新年的第一份成就!

感谢各位支持的客官&#xff0c;感谢你的支持。新年的博客入榜第4名。感谢各位大大的支持。 感谢csdn。感谢cctv。感谢~~特此感谢&#xff1a;很开门的源码继续免费送感谢每一位支持我的朋友&#xff01;2025年的钟声敲响之际&#xff0c;我收到了一个令人振奋的消息——我的博…

作者头像 李华
网站建设 2026/10/23 10:32:52

GogoAI自助健身门店解决方案实战 技术实现与落地部署全流程指南

GogoAI自助健身门店解决方案实战 技术实现与落地部署全流程指南 一、系统架构设计与技术选型 GogoAI自助健身门店解决方案的核心在于构建一个具备自动化管理能力的智能系统&#xff0c;涵盖用户交互、设备控制、数据采集与分析等多个模块。在技术架构上&#xff0c;通常采用前后…

作者头像 李华