ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

杭电操作系统实验:银行家算法等5个核心实验复现与验收避坑指南

杭电操作系统实验:银行家算法等5个核心实验复现与验收避坑指南 简介面向杭州电子科技大学操作系统课程实验的完整资源包覆盖实验要求中的1/2/3/5四个项目setName/setNice、petree输出进程、模拟shell、进程通信与文件系统。所有源代码与实验报告均已通过验收适合正在完成同类实验或复习操作系统核心机制的本科生参考。包内共46个文件以C/C源代码为主21个c、2个cpp并配有6个makefile便于重新编译4个头文件组织模块7份docx实验报告详细记录设计思路与运行结果另有PDF、MD说明文档及可执行文件整体压缩包仅4.41MB结构紧凑。目前已有3690人学习下载热度较高。除课内实验外还额外提供在线PTA编程题目的实现进程模拟、模拟进程调度、银行家算法代码均调试通过可直接运行或对照学习。无论是完成期末验收还是准备操作系统笔试都能从中获得完整可复现的实践参考。1. 杭电操作系统实验为什么这份通过验收的资料值得照着复现杭电的操作系统实验跟很多学校的写法不一样不是背概念而是要求你在 Linux 环境下用 C 语言把操作系统的几大核心机制亲手模拟一遍进程调度、死锁避免、页面置换、磁盘调度每一个都要跑出数据、截出结果再由老师现场换一组输入来验证。很多人代码能跑通可真到验收的时候老师换一组数据就卡壳问题往往不在语法而在于根本没搞清算法在边界输入下会怎么走。这份已通过验收的实验资料源码、运行截图、实验报告都齐每个实验都能照着复现也可以在此基础上改参数、换策略形成一份逻辑完整、不怕追问的验收材料。适合正在赶杭电操作系统实验的同学也适合想拿 Linux C 练手操作系统核心算法的从业者。2. 实验全景五个必做实验、验收点和知识点对照表2.1 进程调度实验先来先服务与短作业优先怎么取舍进程调度实验是所有操作系统实验里第一个动手写的。它的背景是 CPU 管理多个进程同时就绪但 CPU 只有一个调度器必须决定谁先上 CPU。实验通常要求实现 FCFS先来先服务、SJF短作业优先、RR时间片轮转三种算法输入是每个进程的到达时间和服务时间输出是调度顺序以及每个进程的开始时间、完成时间、周转时间、带权周转时间。SJF 的平均周转时间理论上最短但有一个很现实的副作用不断有短作业插队长作业可能一直得不到执行也就是“饥饿”。实验报告里如果能把这个现象用一组具体数据演示出来验收老师会高看一眼。资料里给出的常见做法是纯 C 的结构体数组实现进程用 PCB 结构体描述包含 pid、到达时间、服务时间、剩余时间几个字段状态用宏定义不用链表优点是逻辑直观、改输入方便。时间片轮转需要维护就绪队列用数组配合头尾指针模拟写的时候最容易出问题的点是下标越界这个我在第四章避坑里会单独说。2.2 银行家算法实验死锁避免的核心是安全性检查银行家算法是 Dijkstra 提出的死锁避免算法原理是每个进程在开始前声明自己最大需要多少资源系统只在“分配后仍能找到安全序列”的前提下才真正分配。安全序列的意思是存在一个进程执行顺序让每个进程都能依次拿到它需要的资源并最终完成释放。这个逻辑比死锁检测要前置它是分配之前做预判而不是等死锁发生了再去解环。实验要实现两个核心函数安全性检查 is_safe 和资源请求 request。后者内部要做试探性分配先把资源临时划给进程调用 is_safe 判断不安全就把资源全部退回。资料里的实现把 Need Max - Allocation 单独算成一张矩阵避免每次判断都现场做减法request 函数里“先减后加回滚”的写法是老师最爱问的细节你要能解释清楚“为什么在内存里模拟分配而不是直接改系统状态”。这里先留一句话回滚不是后悔药是银行家算法区别死锁检测的分水岭后面代码里你会看得更明白。2.3 页面置换实验LRU 和 Clock 到底差在哪页面置换属于内存管理实验背景是虚拟内存的分页机制物理内存只有有限几个页框进程访问的页面不在内存里时会产生缺页中断必须从磁盘换入如果内存已满就要先换出一个页面。实验一般要求实现 OPT、FIFO、LRU、Clock 四种算法统计缺页次数和缺页率。这里有一个必须写进报告的结论FIFO 会出现 Belady 异常也就是增加页框数缺页率反而上升。教科书上那个经典引用串 1,2,3,4,1,2,5,1,2,3,4,5 就很有说服力页框数 3 时FIFO 缺页 9 次LRU 缺页 10 次OPT 缺页 7 次页框数加到 4FIFO 反而变成 10 次OPT 降到 6 次。这个反直觉现象就是 FIFO 没有考虑页面局部性带来的恶果。而 LRU 理论上缺页率最低但真正意义上的 LRU 要记录每个页面最近一次访问时间硬件开销太大实验里一般用 Clock 算法近似实现靠一个循环指针加访问位来找可替换页面。资料里 Clock 的代码用的是环形数组和访问位比较贴教材老师一问“你这套跟课本 LRU 有什么区别”就可以答“课本是理论 LRU实验实现的是改进型 Clock”一句话就能把概念边界划清楚。2.4 磁盘调度与进程同步SCAN 和生产者消费者的加分项剩下的两个实验一般是一个磁盘调度再加一个进程同步。磁盘调度模拟磁头移动常见算法是 FCFS、SSTF、SCAN、CSCAN。SCAN 也叫电梯算法磁头朝一个方向移动沿途响应请求到了边界再掉头这样能避免 SSTF 的“磁头被局部请求牵着走、远处的请求饿死”的问题。CSCAN 只在一个方向响应返回起点时快速移动不服务模拟的是循环电梯边界处理和 SCAN 不一样写的时候要单独判断方向切换点。进程同步实验最典型的是生产者消费者问题用信号量或条件变量控制缓冲区存取。写的时候要注意 P 操作的顺序不能颠倒先申请空闲缓冲区再申请互斥锁反过来两个生产者可能互相死锁。这是教材上没有明说、但实验里最容易翻车的地方。资料里这部分同时给了 pthread 信号量版本和纯 C 模拟版本前者跑真实线程现象直观后者只打印状态序列适合做数据推演。建议优先跑 pthread 版本肉眼能看到阻塞和唤醒。2.5 对照表之外的一点经验把这五个实验串起来其实就是汤小丹《计算机操作系统》课本里 CPU 管理、内存管理、设备管理和进程同步四章的缩影。我的建议是不要按老师发的清单从头写到尾而是先写银行家算法它逻辑最完整、数据规模小、最容易被追问跑通它之后你对“安全序列”的理解会直接反哺到进程调度和页面置换的验证里然后再写进程调度这两个实验都有明确的数据表格可以自检做完它们剩下三个的心态就稳了。实验核心知识点关键输出验收必问点进程调度FCFS / SJF / RR 调度策略调度顺序、周转时间、带权周转时间为什么 SJF 平均周转时间最短但可能饿死长进程银行家算法死锁避免、安全性检查安全序列、分配/拒绝结果安全序列怎么找、回滚为什么必要页面置换OPT / FIFO / LRU / Clock缺页次数、缺页率Belady 异常、LRU 和 Clock 的区别磁盘调度SSTF / SCAN / CSCAN磁头移动顺序、总寻道长度SCAN 的电梯行为与边界处理生产者消费者信号量、互斥、同步缓冲区状态变化序列P 操作顺序反了会怎样3. 银行家算法完整复现源码、参数说明与三组测试3.1 数据结构与矩阵初始化P、R 和三个数组银行家算法的数据模型很固定P 是进程数R 是资源种类数四个数组分别是 available当前可用资源、max每个进程对每类资源的最大需求、allocation已分配、need还需要的资源。need 不单独输入而是由 max 减 allocation 得到。这一步看起来简单但很多人会写错方向把 need 算成 allocation 减 max导致负数一片后面安全性判断全部失效。#include stdio.h #define P 5 // 进程数 #define R 3 // 资源种类数 int available[R] {3, 3, 2}; int max[P][R] { {7, 5, 3}, {3, 2, 2}, {9, 0, 2}, {2, 2, 2}, {4, 3, 3} }; int allocation[P][R] { {0, 1, 0}, {2, 0, 0}, {3, 0, 2}, {2, 1, 1}, {0, 0, 2} }; int need[P][R]; int finish[P]; void init_need(void) { for (int i 0; i P; i) for (int j 0; j R; j) need[i][j] max[i][j] - allocation[i][j]; }这里 P 和 R 用宏定义是为了后续改造方便。资料对应的实验题目里老师给的输入可能就是 5 进程 3 类资源你复现时如果想验证别的数据只需要改宏和矩阵值不需要动函数逻辑。init_need 里的双重循环外层遍历进程 i内层遍历资源 j顺序不能写反否则就是按资源优先遍历结果矩阵虽然数值碰巧对但逻辑上会给后面加功能埋坑。3.2 is_safe 安全性检测安全序列怎么找安全性检查是银行家算法的灵魂。它的逻辑是先复制一份可用资源到 work 数组然后反复扫描所有未完成的进程只要某个进程的 need 全部不大于 work就认为它能执行完执行完把它占用的 allocation 加回 work继续下一轮。如果某一轮一个进程都没推进说明剩下的进程全部等资源系统不安全。int is_safe(int safe_seq[]) { int work[R]; int done 0; for (int j 0; j R; j) work[j] available[j]; for (int i 0; i P; i) finish[i] 0; while (done P) { int found 0; for (int i 0; i P; i) { if (finish[i]) continue; int can_alloc 1; for (int j 0; j R; j) { if (need[i][j] work[j]) { can_alloc 0; break; } } if (can_alloc) { for (int j 0; j R; j) work[j] allocation[i][j]; finish[i] 1; safe_seq[done] i; found 1; break; } } if (!found) return 0; } return 1; }这个实现的复杂度是 O(P²·R)最坏情况下每找到一个可执行进程都要从头再扫一遍所有进程。代码里 break 是关键字找到可执行进程后跳出内层 for重新从 0 号进程开始扫。不 break 也没有问题但会多做无效比较加了 break 会让“安全序列”更接近人为推演时选择的顺序报告里写出来的安全序列和代码输出能对得上。work 数组是局部拷贝不是直接改 available这点要记住它和 request 里的回滚机制配合才构成完整的银行家算法。3.3 request 资源请求试探分配与回滚请求分配函数是银行家算法里动作最多的地方。它接收进程号和请求向量先检查请求是否超过 need再检查是否超过 available两项都通过后做试探性分配然后调用 is_safe。安全就留下新的状态不安全就把刚才减掉、加上的值全部还原。int request(int pid, int req[]) { for (int j 0; j R; j) { if (req[j] need[pid][j]) return -1; if (req[j] available[j]) return -2; } // 试探性分配 for (int j 0; j R; j) { available[j] - req[j]; allocation[pid][j] req[j]; need[pid][j] - req[j]; } int safe_seq[P]; if (is_safe(safe_seq)) return 1; // 不安全回滚刚才的分配 for (int j 0; j R; j) { available[j] req[j]; allocation[pid][j] - req[j]; need[pid][j] req[j]; } return 0; }返回值设计成三种1 表示分配成功0 表示分配后不安全已回滚-1 和 -2 分别表示请求超过最大需求和当前可用资源不足。这里的回滚不是捕获异常那种“后悔药”而是算法本身的一部分每次分配都是一次试探只有通过了 is_safe 的验证这个分配才会被保留。老师问“你这个回滚有什么意义”标准回答是它保证系统永远不会真正进入不安全状态因为每个分配动作都先验证后生效。3.4 编译运行与结果解读把输出和自己推演对上测试主函数要覆盖三条路径初始安全状态、合法且安全的请求、合法但会导致不安全的请求。我用了两段请求来做验证第一段是教材里最经典的 P1 请求第二段是特意构造的在 P1 分配完成后请求资源推演结果应为不安全。int main(void) { init_need(); printf(初始状态安全性检查:\n); int seq[P]; if (!is_safe(seq)) { printf( 系统不安全\n); return 1; } printf( 系统安全安全序列: ); for (int i 0; i P; i) printf(P%d , seq[i]); printf(\n\n); int req1[R] {1, 0, 2}; if (request(1, req1) 1) { printf(P1 请求 (1,0,2): 分配成功\n); if (is_safe(seq)) { printf( 验证: 当前安全序列: ); for (int i 0; i P; i) printf(P%d , seq[i]); printf(\n); } } else { printf(P1 请求 (1,0,2): 分配失败\n); } printf(\n); int req2[R] {0, 2, 0}; int r request(0, req2); if (r 1) printf(P0 请求 (0,2,0): 分配成功\n); else if (r -1) printf(P0 请求 (0,2,0): 超过最大需求拒绝\n); else if (r -2) printf(P0 请求 (0,2,0): 可用资源不足拒绝\n); else printf(P0 请求 (0,2,0): 分配后会进入不安全状态回滚拒绝\n); return 0; }编译命令建议固定成gcc -Wall -o banker banker.c然后./banker运行。预期输出里初始安全序列是 P1 P3 P4 P0 P2P1 请求后安全序列保持不变P0 请求后输出“分配后会进入不安全状态回滚拒绝”。这个测试数据的好处是每一步结果都能用手推一遍推完再对照程序输出基本就能确认代码没写偏。如果你复现的资料里初始数据不同也没关系只要把 max 和 allocation 改成你题目里的值重新跑一遍再手动算一次安全序列对齐就行。4. 验收避坑五个最容易翻车的细节现象原因都讲清4.1 老师换一组数据程序直接死循环现象样例数据跑得好好的老师随手改了 arrive time 或 need 矩阵程序卡住不输出或者输出到一半停住。原因is_safe 或调度模拟的 while 循环里没有“无进展即退出”的判断。银行家算法的 while 循环如果一轮扫下来一个进程都没推进说明系统不安全此时必须返回 0进程调度的模拟循环如果没有判断所有进程是否都完成时间片轮转就可能无限转下去。这个 bug 很隐蔽因为样例数据恰好都是安全状态推进总是顺利进行。解决检查所有循环找到“本轮是否有进展”的布尔标志。本文的 is_safe 实现里if (!found) return 0;就是干这个的。自己写代码时凡是 while 里嵌套 for 扫描的都要加这个标志。这个坑是最典型的“换数据翻车”没有之一。4.2 在 Windows 上写好到 Linux 跑不起来现象自己电脑上 Code::Blocks 编译通过实验机的 gcc 报错一大片或者编译过了运行时报“段错误”。原因用了conio.h、windows.h、system(pause)这类非 POSIX 头文件或函数Linux 下根本没有另一方面是数组下标越界Windows 下编译器不报错Linux 下运行时直接段错误。解决从资料里复现代码时守住一条规矩只用stdio.h、stdlib.h、string.h暂停用getchar()不要用system(pause)。编译统一用gcc -Wall -o 程序名 源文件.c看到 warning 也要处理掉尤其是数组越界的警告。实验机环境未知代码越朴素越安全这也是我推荐资料里纯 C 实现的原因。4.3 截图截了一大片却说不出结果是什么现象报告里贴了一张巨大的全屏截图终端输出区只占一小块老师扫一眼看不出跑的是哪个实验结论是什么。原因截图没有裁剪也没有配文字说明验收时老师没时间在一堆像素里找输出窗口。解决每个实验结果只截终端窗口不要截整个桌面截图下方紧跟一行说明格式固定为“输入xxx算法xxx输出调度顺序 / 缺页率 / 安全序列”。资料里的报告模板每张图都有这一行说明可以直接套用。还有一个小技巧终端背景用浅色截图会比深色背景清楚打印出来也不费墨。4.4 被问算法复杂度当场答不上来现象代码能跑报告也有图但老师随口一句“银行家算法复杂度多少”整个人愣住。原因只背了代码没推导复杂度也没有把复杂度写进报告。解决is_safe 最坏情况下每个进程被扫描多轮所以要记住 O(P²·R)空间上额外用了 work 和 safe_seq一个是长度 R 的数组一个是长度 P 的数组空间复杂度 O(P R)。进程调度里FCFS 和 SJF 排序部分是 O(n log n)RR 的时间片轮转是 O(n) 级别的模拟。页面置换和磁盘调度同理每个算法在报告里用一行“时间复杂度xxx”写明验收时这就是送分题。4.5 把死锁避免写成了死锁检测现象代码里没有分配前的安全性判断而是分配后定期扫描一遍发现存在环形等待就叫“检测到死锁”。原因把银行家算法和死锁检测搞混了。死锁检测是事后发现银行家是事前避免两者处理时机完全不同。解决确认你的 request 函数里有“试探分配 → is_safe → 回滚”三个动作缺回滚就是假避免。自己验证的方法很简单手动构造一个会导致不安全的请求如果程序没有拒绝、而是继续分配说明你的实现本质是检测不是避免老师只要换一个不安全测试用例就能测出来。这一步是验收里的“生死线”务必自己先测一遍。5. 把资料改造成自己的报告结构与代码去重技巧5.1 实验报告五段式每个部分放什么杭电的实验报告格式在不同学期会有微调但骨架基本是五段式实验目的、实验内容、关键代码、运行结果、实验心得。第一段要写成“验证xxx算法的正确性理解xxx机制”不要写空话第二段描述你实现了哪些功能用什么语言什么数据结构第三段只放核心函数不要贴全量代码第四段放截图和数据表第五段写你在调试中遇到的问题和解决过程。资料里的报告模板可以直接拿来当骨架但要注意两点一是实验心得部分老师会重点看“遇到什么问题、怎么解决的”这就是第四章那些坑挑一两个真实写进去二是代码注释手写代码时在关键函数上方加一行注释说明这个函数负责什么比大段注释更有用。5.2 截图与对比数据让老师第一眼看到结论运行结果不要只贴一张图。进程调度实验把 FCFS、SJF、RR 三组周转时间做成一个三行三列的对比表银行家算法把 P1 请求成功和 P0 请求拒绝两次截图并排放旁边各加一行说明页面置换把四种算法的缺页率放在同一张表里单独标出 Belady 异常的那一行。老师翻报告时扫表格的速度远快于读代码数据表就是你的结论。我自己写报告的习惯是先做完所有实验再统一截图最后写文字。截图时把终端窗口调到宽度 80 字符左右字体大小调到能看清输出内容多就分两张图不要缩小字号硬塞。每张图都带“图 1xxx”下面紧跟两行说明说明里写输入和结论不写废话。5.3 代码去重三板斧结构、输入、输出资料里的代码能直接跑但如果你想改成自己的版本也有成本低效果好的三条路第一改数据结构。数组改成结构体数组或者反过来。银行家算法里把 max、allocation、need 三个二维数组合并成一个结构体数组每个进程是一个结构体代码的访问方式就完全变了。第二改输入方式。硬编码矩阵改成从文件读或者加一个交互菜单让用户在程序里输入进程数和资源数然后逐行输入矩阵。第三改输出格式。把纯文本输出改成类似表格的对齐格式或者在每行输出前加算法名称前缀。这三板斧做完代码结构和原版已经有明显差异。再配合自己写注释、换变量名就不会出现整片雷同的问题。我更建议的是先把资料里的代码完整跑通一遍然后关掉资料自己重新写一个版本遇到卡住再回头对照。这样写出来的代码才是你自己的验收时被追问也能讲得清思路。6. 验收前 15 分钟每个实验准备一个追问回答6.1 进程调度与同步的追问调度实验最常见的追问是“为什么 SJF 平均周转时间最短”。回答要点短作业先执行长作业的等待时间没有因为之后才开始而变长反而短作业完成的早拉低了总体平均等待时间。另一个高频问题是“RR 的时间片设多大合适”答案是时间片要远大于一次进程切换的开销又要小于大多数进程的 CPU 突发时间否则退化成 FCFS 或者频繁切换。生产者消费者实验的追问集中在“为什么先 P(empty) 再 P(mutex)”。可以这样答如果先 P(mutex) 再 P(empty)缓冲区满时生产者持有锁去等空位消费者需要锁却进不来就形成了互相等待相当于自己制造了死锁。这个因果关系一句话说清楚比背概念有用得多。6.2 内存管理三个实验的追问页面置换被问最多的是 Belady 异常。回答模板FIFO 替换掉的页面可能是马上要用的页面增加页框数后被替换的时间点发生变化可能让更多近期要用的页面被换出所以缺页率反而上升。LRU 和 Clock 的区别核心一句就是LRU 需要精确记录访问时间Clock 只用一位访问位近似性能接近但实现成本低。磁盘调度如果被问到 SCAN 的边界就强调磁头到达边界才掉头CSCAN 到边界后快速移动回来、过程中不服务。整个验收过程中老师问的所有问题本质上都在验证一件事你是真的理解了算法行为还是背了一份答案。所以我在每次验收前都会把每个实验的“算法选型原因、时间复杂度、边界输入后果”用三句话快速复述一遍确认自己能不看资料讲出来再走进实验室。这个方法救了我好几次希望帮到你。本文还有配套的精品资源点击获取
返回列表