西南科技大学操作系统实验2:cpp2.cpp 调度与银行家算法解析 简介这份资源是西南科技大学计算机操作系统课程实验2的配套代码面向正在学习操作系统原理、需要动手实践的高校学生。实验围绕进程管理、内存管理、文件系统、设备管理、死锁预防与避免、线程与并发、系统调用等核心模块展开帮助读者把调度算法、页面置换、银行家算法等抽象概念落到编程实现上。压缩包内共1个文件为cpp源码体积约1KB轻量便于直接编译运行和对照调试。目前已有2061人学习下载说明该实验在课程学习中具有较高的参考价值。读者可借助这份代码理解实验要求与实现思路梳理进程调度、资源分配等关键流程并在此基础上完成自己的实验报告与结果分析为后续系统级开发打下基础。1. 拆开西南科技大学操作系统实验2一个 cpp2.cpp 能跑通哪些核心机制如果你手头正好有一份计算机操作系统实验2.rar解压后只看到一个cpp2.cpp第一反应大概率是“就这”——一个源文件能覆盖操作系统实验的什么内容但真正翻过西南科技大学这门课实验指导书的人会知道实验2通常不是让你从零写一个内核而是用 C 在用户态模拟操作系统的某个核心子系统把进程调度、内存置换或银行家算法这类“纸面流程”变成可观察、可打印、可对比的数据。这个压缩包的价值不在于代码量而在于它把课堂上的调度算法、资源分配策略和状态迁移逻辑压缩进一个可编译、可改参数的工程骨架里。它适合三类人正在跟西南科技大学计算机操作系统课程进度、需要对照实验2验收标准的学生想找一个能直接跑起来的调度/分配算法参考实现的自学者以及需要快速验证 FCFS、SJF、LRU、银行家算法等经典策略输出是否符合预期的开发者。你不需要先搭 Linux 0.11 实验环境也不需要配 QEMU 或 Bochs只要本地有 g 或 Dev-C 就能把cpp2.cpp编译成可执行文件输入测试数据观察每一步的状态变化。接下来我会按“先确认代码结构再补全输入输出最后调参验证”的顺序把这份资源拆成能直接抄作业的步骤。2. 从 cpp2.cpp 反推实验2的代码骨架先定位它模拟的是哪类子系统2.1 看头文件与全局结构判断是调度器还是分配器拿到cpp2.cpp后不要急着编译先看前 30 行。操作系统实验2的常见实现分两支一支是进程调度模拟核心数据结构是 PCB 结构体或类字段包括进程名、到达时间、服务时间、优先级、状态另一支是内存管理或银行家算法核心结构是资源向量、可用资源数组、最大需求矩阵和分配矩阵。判断方法很简单如果代码里出现readyQueue、running、arrivalTime、burstTime那就是调度类如果出现Available、Max、Allocation、Need那就是银行家算法或死锁避免类如果出现pageFrame、pageTable、LRU、FIFO那就是页面置换类。我一般会先扫一遍全局变量和结构体定义把字段名抄到纸上再对照实验指导书里的算法描述。这一步决定了后面输入数据的格式调度类需要输入进程数量和各进程的到达时间、服务时间银行家算法需要输入资源种类数、进程数、各类资源总量以及每个进程的最大需求矩阵。如果字段名和指导书对不上以代码为准因为验收时跑的是这份代码不是指导书上的伪代码。// 典型调度类 PCB 结构字段名可能略有差异 struct PCB { string name; // 进程名如 P1、P2 int arrivalTime; // 到达时间 int burstTime; // 服务时间/运行时间 int priority; // 优先级数值越小优先级越高或反之看比较逻辑 int startTime; // 开始执行时间 int finishTime; // 完成时间 int turnaroundTime; // 周转时间 finishTime - arrivalTime int waitingTime; // 等待时间 turnaroundTime - burstTime string state; // 状态就绪、运行、完成 };这段结构体是调度类实验的通用骨架。关键参数是arrivalTime和burstTime它们决定调度顺序priority字段只在优先级调度里起作用如果代码里没有比较priority的分支说明这份cpp2.cpp可能只实现了 FCFS 或 SJF。周转时间和等待时间通常是输出统计量验收时会检查这两个值是否算对。如果代码里没有startTime字段那它可能用finishTime倒推或者直接在调度过程中累加这时候你要注意输出格式是否和指导书要求一致。2.2 编译与首次运行把默认输入跑通再改数据确认结构后直接用 g 编译。Windows 下如果装了 MinGW命令是g cpp2.cpp -o cpp2.exeLinux 或 macOS 下是g cpp2.cpp -o cpp2。编译报错先看是不是用了bits/stdc.h而编译器不支持换成iostream、vector、algorithm等具体头文件即可。如果报cin、cout未定义检查有没有using namespace std;或手动加std::。# 编译命令注意文件名大小写 g cpp2.cpp -o cpp2 # 运行先按默认提示输入 ./cpp2首次运行不要自己编数据先看程序提示什么。常见提示是“请输入进程数”“请输入进程名 到达时间 服务时间”或“请输入资源种类数”。如果程序直接输出结果没有交互说明数据硬编码在代码里你需要找到main函数里的数组初始化部分把测试数据替换成指导书上的用例。这一步的目的是确认代码能跑通而不是验证算法对不对。跑通后再用指导书上的标准输入输出对比如果结果不一致再回头查算法实现。2.3 输入数据格式与参数含义别把到达时间和服务时间填反调度类实验最常见的翻车点是把到达时间和服务时间填反。到达时间是进程进入就绪队列的时刻服务时间是它需要占用 CPU 的时长。FCFS 按到达时间排序SJF 按服务时间排序优先级调度按优先级排序。如果你填反了输出结果会完全对不上。银行家算法类实验的输入更复杂通常需要先输入资源种类数m和进程数n然后输入Available向量、Max矩阵、Allocation矩阵程序会计算Need Max - Allocation并判断安全性。// 银行家算法典型输入读取片段 int n, m; cout 请输入进程数和资源种类数; cin n m; vectorint available(m); cout 请输入 Available 向量 endl; for (int i 0; i m; i) cin available[i]; vectorvectorint maxNeed(n, vectorint(m)); cout 请输入 Max 矩阵 endl; for (int i 0; i n; i) for (int j 0; j m; j) cin maxNeed[i][j]; vectorvectorint allocation(n, vectorint(m)); cout 请输入 Allocation 矩阵 endl; for (int i 0; i n; i) for (int j 0; j m; j) cin allocation[i][j];这段代码的关键参数是n和m它们决定后面矩阵的维度。Available是系统当前可用的各类资源数量Max是每个进程对各类资源的最大需求Allocation是已经分配给每个进程的资源。程序内部会计算Need矩阵然后跑安全性算法找一个Need小于等于Available的进程假设它执行完并释放资源再更新Available重复直到所有进程都能执行完或找不到可执行进程。如果找不到说明系统处于不安全状态。输入时注意矩阵按行输入每行对应一个进程列对应资源种类。3. 把调度算法跑出可对比结果FCFS、SJF 与优先级调度的参数差异3.1 FCFS 与 SJF 的排序逻辑差异改一行比较函数就能切换FCFS 和 SJF 的代码骨架几乎一样区别只在就绪队列的排序规则。FCFS 按到达时间升序SJF 按服务时间升序。如果你拿到的cpp2.cpp只实现了其中一种想改成另一种只需要改sort的比较函数或优先队列的比较器。常见做法是定义一个compare函数然后在每次从就绪队列取进程时调用。// FCFS 比较到达时间早的优先 bool fcfsCompare(const PCB a, const PCB b) { if (a.arrivalTime ! b.arrivalTime) return a.arrivalTime b.arrivalTime; return a.name b.name; // 到达时间相同按进程名排序保证输出稳定 } // SJF 比较服务时间短的优先服务时间相同按到达时间排序 bool sjfCompare(const PCB a, const PCB b) { if (a.burstTime ! b.burstTime) return a.burstTime b.burstTime; return a.arrivalTime b.arrivalTime; }参数说明arrivalTime越小越早进入就绪队列burstTime越小越适合 SJF。注意 SJF 分抢占式和非抢占式非抢占式是当前进程运行完才重新选抢占式是每次有新进程到达就比较剩余服务时间。如果代码里没有remainingTime字段那它大概率是非抢占式。验收时老师可能会问“如果两个进程服务时间相同怎么办”这时候按到达时间排序是常见做法按进程名排序是为了输出稳定避免每次运行结果不一样。3.2 优先级调度的坑数值方向与抢占条件优先级调度最容易搞错的是优先级数值方向。有的代码里数值越小优先级越高有的反过来。判断方法是看比较函数里用的是还是。如果代码里写的是a.priority b.priority那就是数值小优先如果写的是a.priority b.priority那就是数值大优先。这个方向搞反了输出顺序完全颠倒。另一个坑是抢占条件优先级调度可以是抢占式也可以是非抢占式。抢占式会在新进程到达时比较优先级如果新进程优先级更高就抢占 CPU非抢占式则等当前进程运行完再比较。// 优先级调度比较假设数值越小优先级越高 bool priorityCompare(const PCB a, const PCB b) { if (a.priority ! b.priority) return a.priority b.priority; // 数值小优先 return a.arrivalTime b.arrivalTime; // 优先级相同按到达时间 }如果你要改抢占式需要在每次新进程到达时检查newProcess.priority currentProcess.priority如果成立就切换。这通常需要维护一个remainingTime数组记录每个进程还剩多少服务时间。非抢占式则简单得多只需要在就绪队列里按优先级排序当前进程运行完再取队首。我一般会先用非抢占式跑通再改抢占式因为抢占式的状态打印更容易乱。3.3 输出统计量验证周转时间、等待时间与带权周转时间调度类实验的输出通常包括每个进程的开始时间、完成时间、周转时间、等待时间和带权周转时间。周转时间 完成时间 - 到达时间等待时间 周转时间 - 服务时间带权周转时间 周转时间 / 服务时间。这三个值算错一个验收就可能不过。常见错误是用开始时间减到达时间当等待时间实际上等待时间应该是周转时间减服务时间因为周转时间包含了服务时间本身。// 计算并打印统计量 for (auto p : processes) { p.turnaroundTime p.finishTime - p.arrivalTime; p.waitingTime p.turnaroundTime - p.burstTime; double weightedTurnaround (double)p.turnaroundTime / p.burstTime; cout p.name 完成时间 p.finishTime 周转时间 p.turnaroundTime 等待时间 p.waitingTime 带权周转时间 weightedTurnaround endl; }参数说明finishTime是进程执行完毕的时刻arrivalTime是到达时刻burstTime是服务时间。带权周转时间用double避免整数除法截断。如果输出要求保留两位小数用printf(%.2f, weightedTurnaround)或cout fixed setprecision(2)。验证方法很简单把所有进程的等待时间加起来除以进程数就是平均等待时间平均周转时间同理。如果这两个值和指导书上的参考答案对不上先检查排序逻辑再检查时间累加逻辑。4. 银行家算法与页面置换从矩阵输入到安全序列输出4.1 银行家算法的安全性检查流程银行家算法的核心是安全性检查维护Work向量初始等于Available和Finish数组初始全 false然后循环找满足Need[i][j] Work[j]且Finish[i] false的进程。找到后假设它执行完Work[j] Allocation[i][j]Finish[i] true并把进程加入安全序列。如果一轮下来找不到任何可执行进程且还有Finish[i] false的进程说明系统不安全。bool isSafe(vectorint available, vectorvectorint maxNeed, vectorvectorint allocation, vectorint safeSeq) { int n maxNeed.size(), m available.size(); vectorint work available; vectorbool finish(n, false); safeSeq.clear(); while (safeSeq.size() n) { bool found false; for (int i 0; i n; i) { if (finish[i]) continue; bool canRun true; for (int j 0; j m; j) { if (maxNeed[i][j] - allocation[i][j] work[j]) { canRun false; break; } } if (canRun) { for (int j 0; j m; j) work[j] allocation[i][j]; finish[i] true; safeSeq.push_back(i); found true; } } if (!found) return false; // 找不到可执行进程不安全 } return true; }参数说明available是当前可用资源maxNeed是最大需求矩阵allocation是已分配矩阵safeSeq用来存安全序列。Need矩阵没有单独存储而是用maxNeed[i][j] - allocation[i][j]实时计算。注意work是available的副本不能直接改available否则会影响后续判断。如果返回 false说明系统处于不安全状态但并不意味着死锁已经发生只是说存在死锁风险。4.2 页面置换算法的命中率对比LRU 与 FIFO 的差异页面置换类实验通常要求实现 FIFO 和 LRU 两种算法并对比缺页率。FIFO 用队列维护页面进入内存的顺序淘汰队首页面LRU 用栈或时间戳维护页面最近使用情况淘汰最久未使用的页面。两者的代码骨架相似区别在淘汰策略。// FIFO 页面置换 int fifo(vectorint pages, int frameCount) { queueint q; unordered_setint inMemory; int faults 0; for (int page : pages) { if (inMemory.count(page)) continue; // 命中 faults; if (inMemory.size() frameCount) { int victim q.front(); q.pop(); inMemory.erase(victim); } q.push(page); inMemory.insert(page); } return faults; } // LRU 页面置换用 list 维护最近使用顺序 int lru(vectorint pages, int frameCount) { listint recent; unordered_mapint, listint::iterator pos; int faults 0; for (int page : pages) { if (pos.count(page)) { recent.erase(pos[page]); // 命中则移到最前 } else { faults; if (recent.size() frameCount) { int victim recent.back(); recent.pop_back(); pos.erase(victim); } } recent.push_front(page); pos[page] recent.begin(); } return faults; }参数说明pages是页面访问序列frameCount是物理块数量。FIFO 的缺页率不一定比 LRU 高存在 Belady 异常即增加物理块数缺页率反而上升。LRU 通常缺页率更低但实现开销更大。验证方法是用同一组页面序列分别跑两个算法输出缺页次数和缺页率缺页率 缺页次数 / 总访问次数。如果结果和指导书参考答案不一致先检查页面序列有没有读错再检查物理块数量有没有传对。5. 避坑与排查cpp2.cpp 编译运行中最容易翻车的五个点5.1 现象编译报错‘string’ was not declared原因代码用了string但没有包含string头文件或者没有using namespace std;。有些老版本 Dev-C 默认包含部分头文件换到 g 就报错。解决在文件开头加#include string和#include iostream并确认using namespace std;存在。如果不想用using namespace std;把string改成std::stringcout改成std::cout。5.2 现象输入进程数后程序直接结束没有输出原因cin读取失败或输入格式不对。比如程序要求输入“P1 0 5”你输入了“P1,0,5”逗号会导致读取失败。解决严格按提示格式输入进程名和数字之间用空格分隔。如果程序要求先输入进程数再输入每个进程信息不要一次性粘贴多行逐行输入更稳。另外检查cin n后有没有cin.ignore()清除换行符否则后面的getline会读到空行。5.3 现象调度结果中某个进程的完成时间小于开始时间原因时间累加逻辑写反了或者finishTime没有正确更新。常见错误是在非抢占式调度里当前进程运行完才更新finishTime但代码里提前更新了。解决找到调度循环确认finishTime startTime burstTime只在进程真正执行完时赋值。如果是抢占式finishTime应该是最后一次被调度执行完的时刻不是第一次开始执行的时刻。5.4 现象银行家算法输出“安全”但安全序列为空原因safeSeq没有在找到可执行进程时push_back或者safeSeq在函数返回前被清空。解决检查isSafe函数里safeSeq.push_back(i)是否在finish[i] true之后执行。另外确认调用方有没有在输出前重新初始化safeSeq。如果安全序列长度不等于进程数说明循环条件写错了应该是while (safeSeq.size() n)而不是while (true)。5.5 现象LRU 缺页次数比 FIFO 还多原因LRU 的“最近使用”顺序维护反了把最久未使用的页面当成了最近使用的。解决检查recent列表的操作命中时应该把页面移到最前recent.erase(pos[page]); recent.push_front(page);淘汰时应该取最后一个recent.back()。如果命中时没有移动那就退化成了 FIFO。另外确认pos映射在淘汰后有没有正确erase否则下次访问会拿到失效迭代器。6. 进阶验证用脚本批量跑测试用例并对比参考答案6.1 构造测试用例文件与批量运行手动输入数据跑一次两次还行但要验证多组用例最好把输入写成文件用重定向批量跑。常见做法是建一个testcases目录每个用例一个.in文件然后写个 shell 脚本循环执行。#!/bin/bash # 批量运行 cpp2 并保存输出 for input in testcases/*.in; do name$(basename $input .in) ./cpp2 $input outputs/${name}.out echo 已运行 $name done参数说明testcases/*.in是输入文件每行格式和手动输入一致outputs/目录需要提前创建。运行后逐个对比.out文件和参考答案。如果输出包含随机性比如相同优先级进程顺序不固定先确认代码里有没有按进程名做二次排序没有的话加上保证输出可复现。6.2 用 diff 快速定位输出差异对比输出时不要用眼睛一行行看直接用diff。如果参考答案和你的输出只有空格差异用diff -w忽略空白。如果差异在数值上先检查是不是浮点数精度问题比如带权周转时间保留位数不同。# 忽略空白差异对比 diff -w outputs/case1.out expected/case1.out # 如果只想看差异行 diff -y --suppress-common-lines outputs/case1.out expected/case1.out我一般会先把参考答案复制到expected/目录然后用diff -w跑一遍。如果差异集中在某几个进程的等待时间上回去检查这几个进程的到达时间和服务时间有没有填错。如果差异在安全序列顺序上说明银行家算法找可执行进程的顺序和参考答案不一致但安全序列不唯一这种情况只要序列合法即可不必强求一致。6.3 参数边界测试进程数为 1、服务时间为 0、资源总量为 0验收时老师可能会问边界情况。进程数为 1 时FCFS 和 SJF 结果应该一样服务时间为 0 时周转时间等于等待时间带权周转时间要避免除以零资源总量为 0 时银行家算法应该直接判断不安全除非所有进程的Need都为 0。这些边界用例建议提前跑一遍确认程序不会崩溃或输出nan。// 避免除零带权周转时间计算前检查 burstTime double weightedTurnaround 0.0; if (p.burstTime 0) weightedTurnaround (double)p.turnaroundTime / p.burstTime; else weightedTurnaround 0.0; // 或按指导书要求处理从那以后我每次拿到这类实验代码都会先跑一个进程数为 1 的用例再跑一个服务时间为 0 的用例确认边界不崩才去跑完整数据集。这个习惯帮我省了很多返工时间。希望帮到你。本文还有配套的精品资源点击获取