西南科技大学操作系统实验2:cpp2.cpp单文件解析与进程调度算法实现
2026/9/23 11:32:56 网站建设 项目流程

简介:这份资源是西南科技大学计算机操作系统课程实验2的配套代码,面向计算机科学与技术专业学生及正在学习操作系统原理的自学者,用于通过编程实践加深对进程管理、内存管理、文件系统、设备管理、死锁预防与避免、线程与并发等核心知识的理解。压缩包内共1个文件,为cpp源码,整体约1KB,体量轻便,适合直接编译运行或作为实验参考模板。资源已有2061人学习下载,说明其在课程实验场景中具有一定参考价值。读者可借助该代码对照实验要求,梳理进程调度、同步互斥、系统调用等关键实现思路,并在此基础上完成实验报告中的步骤分析与结果验证,提升编程与调试能力,为后续系统级开发打下基础。

1. 从一份 cpp2.cpp 说起:西南科技大学操作系统实验2到底练什么

如果你手里正躺着一个计算机操作系统实验2.rar,解压后只看到一个cpp2.cpp,第一反应大概率是懵的——一个源文件能撑起一整个实验?我当初拆这类课程包时也是这个反应。这份来自西南科技大学计算机操作系统课程的实验2,走的是典型的“单文件多算法”路线:把进程调度、内存置换、死锁避免这些核心机制塞进一个 C++ 文件里,靠菜单驱动,逐个跑给你看。它解决的不是“造一个操作系统”,而是让你亲手把 FCFS、SJF、LRU、银行家算法这些纸面流程翻译成能跑出数字的代码。适合正在跟这门课、需要交实验报告的人,也适合想拿一份能编译的骨架去改自己调度逻辑的从业者。下面我按“先看懂结构、再动手跑、最后避坑”的顺序拆。

2. 拆开 cpp2.cpp:单文件里藏了哪几套算法骨架

2.1 先认清这份源码的组织方式

拿到cpp2.cpp别急着编译,先通读一遍结构。这类课程实验源码通常不会用工程化的多文件拆分,而是把所有逻辑压在一个main里,用whileswitch做菜单。常见做法是:定义几个struct表示进程控制块(PCB)或页面表项,然后每个算法写成一个独立函数,菜单里选编号调用。你要做的第一件事是找到这几个结构体定义,它们决定了后面所有算法的数据长什么样。

以进程调度为例,PCB 里一般会有进程名、到达时间、服务时间、已运行时间、优先级、状态这几个字段。内存置换的页表项则会有页号、访问位、修改位、驻留标志。看懂字段含义,后面调参数才不会瞎改。我一般会先在纸上把结构体字段抄一遍,标上哪个是输入、哪个是算法运行时算出来的,这样调试时一眼能看出是输入喂错了还是逻辑写错了。

// 典型的 PCB 结构,字段名可能不同,按实际源码对照 struct PCB { char name[10]; // 进程名,输入 int arriveTime; // 到达时间,输入 int serviceTime; // 服务时间,输入 int runTime; // 已运行时间,算法运行时累加 int priority; // 优先级,输入,数值含义看算法约定 char state; // 状态:W 等待 / R 运行 / F 完成 };

这段结构体是整份源码的地基。arriveTimeserviceTime是你在菜单里手动输入或从预设数组读入的原始数据;runTimestate是算法跑起来之后才会变的。很多人第一次跑结果不对,不是算法写错,而是把priority的数值方向搞反了——有的实现里数值越小优先级越高,有的反过来,这个必须回到源码里确认,不能凭印象。

2.2 进程调度算法的实现差异

单文件里最占篇幅的通常是进程调度。FCFS 最好写,按到达时间排个序依次执行就行;SJF 要分“非抢占”和“抢占”两种,非抢占是等当前进程跑完再挑最短的,抢占是每来一个新进程就比较剩余时间;优先级调度同理。多级反馈队列最复杂,要维护多个就绪队列,还要处理时间片用完后的降级。

我建议的阅读顺序是:先只看 FCFS 那个函数,把它的输入输出跑通,确认菜单能正确调用、结果能正确打印。然后再看 SJF,对比它比 FCFS 多了哪几步排序或比较。这样一层层加,比一上来啃多级反馈队列要稳。常见做法是每个算法函数都接收同一个 PCB 数组,返回一个调度序列或直接打印甘特图式的执行顺序。你要留意的是周转时间和带权周转时间这两个指标怎么算的——周转时间 = 完成时间 - 到达时间,带权周转时间 = 周转时间 / 服务时间,这两个数在实验报告里是必须出现的。

// FCFS 核心逻辑示意,按到达时间排序后依次执行 void FCFS(PCB p[], int n) { sort(p, p + n, [](PCB a, PCB b) { return a.arriveTime < b.arriveTime; // 到达早的先执行 }); int currentTime = 0; for (int i = 0; i < n; i++) { if (currentTime < p[i].arriveTime) currentTime = p[i].arriveTime; // CPU 空闲等待 currentTime += p[i].serviceTime; p[i].runTime = p[i].serviceTime; p[i].state = 'F'; // 此处计算并打印周转时间、带权周转时间 } }

这段代码的关键在currentTime的推进方式。如果当前时间小于下一个进程的到达时间,说明 CPU 有空闲,要先把时间跳到到达时间再执行。这个细节在 SJF 和优先级调度里同样存在,漏掉就会导致周转时间算小。参数方面,n是进程数量,通常由菜单输入决定;p[]是全局或主函数传入的数组。改数据时只改arriveTimeserviceTime,别动runTime的初始值。

2.3 内存置换与银行家算法的落点

如果这份实验2还包含内存管理,那cpp2.cpp里大概率有 LRU 或 FIFO 的页面置换模拟。LRU 的核心是记录每个页面最后一次被访问的时间,缺页时淘汰时间最早的那个。实现上可以用一个访问计数数组,每次访问就把对应页面的计数更新为当前时刻。FIFO 更简单,用一个队列,先进先出。银行家算法则是另一套逻辑:维护可用资源向量、最大需求矩阵、已分配矩阵,然后跑安全性检查,找安全序列。

这两块和进程调度的区别在于,它们不涉及时间推进,而是状态转移。调试时重点看缺页次数和安全序列是否唯一。银行家算法的安全序列可能不唯一,只要找到一个就行,但如果你一个都找不到而理论上有解,那多半是Need矩阵算错了——Need = Max - Allocation,这个减法别搞反。

3. 把 cpp2.cpp 跑起来:编译、输入与结果验证

3.1 编译环境与命令

这类课程源码基本是标准 C++,不带图形库,用 g++ 直接编译就行。Windows 上如果装了 MinGW 或者 Dev-C++,也能直接跑。我一般会在命令行里编译,这样报错信息看得清楚。

# Linux / macOS 下编译,-o 指定输出文件名 g++ cpp2.cpp -o os_exp2 # 运行 ./os_exp2

如果编译报错说找不到bits/stdc++.h,说明你用的不是 GCC 系编译器,把它换成具体的头文件,比如<iostream><algorithm><cstring>。如果报sort未定义,检查有没有#include <algorithm>using namespace std;。这些是课程源码最常见的两个编译坑,跟算法本身无关。

3.2 输入数据的组织方式

跑起来之后菜单会问你选哪个算法,然后让你输入进程数量和各进程参数。这里有个血泪经验:输入顺序必须和源码里cin的顺序完全一致。有的源码是先读进程名再读到达时间再读服务时间,有的把优先级插在中间。你输错一个,后面全乱。我一般会先选一个算法,随便输两组数据,看打印出来的原始数据对不对,确认输入解析没问题,再正式跑实验数据。

如果源码用的是预设数组而不是手动输入,那就要找到那个数组定义,直接改里面的数值。改完重新编译。这种方式更适合批量测试,比如你想对比 FCFS 和 SJF 在同一组数据下的周转时间差异,改一次数组跑两次就行。

3.3 结果怎么验证才算对

跑出结果别急着抄进报告,先做两个检查。第一,手工算一遍第一个进程的周转时间,看和程序输出是否一致。第二,看所有进程的完成时间是否单调递增,如果出现后面的进程比前面的先完成,那调度逻辑肯定有问题。对于银行家算法,检查找到的安全序列里每个进程的Need是否都不超过当时的工作向量,这是安全性的定义,逐行核对一遍。

常见做法是拿一组经典数据做回归测试,比如三个进程、到达时间分别为 0、1、2,服务时间为 5、3、2,手工算出 FCFS 和 SJF 的结果,然后跟程序输出对比。这组数据我用了很多次,能同时暴露排序错误和时间推进错误。

4. 避坑与排查:这份实验源码最容易翻车的五个地方

4.1 现象:编译通过但运行直接闪退

原因通常是数组越界。课程源码里经常用固定大小的数组,比如PCB p[10],但菜单允许你输入大于 10 的进程数。或者字符串拷贝时用了strcpy但目标缓冲区不够长。解决方法是找到数组定义,把大小改大,或者在输入进程数后加一个范围检查。我一般会把p[10]改成p[100],一劳永逸。

4.2 现象:周转时间算出来是负数

原因是currentTime没有正确处理 CPU 空闲。当第一个进程的到达时间大于 0 时,如果直接currentTime += serviceTime,完成时间就会小于到达时间,周转时间变成负数。解决方法是执行前先判断if (currentTime < p[i].arriveTime) currentTime = p[i].arriveTime;。这个判断在 FCFS、SJF、优先级调度里都要有,漏一个就错一个。

4.3 现象:SJF 结果和手工算的不一样

先确认你跑的是非抢占还是抢占版本。非抢占 SJF 只在当前进程完成后才重新选择,抢占 SJF 每来一个新进程都要比较剩余时间。源码里如果只写了一种,而实验要求另一种,那结果对不上是正常的。解决方法是看实验指导书要求哪种,然后改源码里的选择时机。另外,SJF 比较的是服务时间还是剩余时间,也要确认,非抢占用服务时间,抢占用剩余时间。

4.4 现象:LRU 缺页次数比 FIFO 还多

这通常不是算法错,而是访问序列的问题。LRU 在特定序列下确实可能比 FIFO 缺页多,这叫 Belady 异常的反面情况。但如果你用的序列是随机的,LRU 一般不会比 FIFO 差太多。先检查访问序列有没有输错,再检查 LRU 的“最近使用”时间戳是不是每次访问都更新了。如果只在缺页时更新,那就退化成了 FIFO。解决方法是确保每次访问命中时也更新时间戳。

4.5 现象:银行家算法找不到安全序列

先检查Available向量有没有减去已经分配的资源。初始Available是系统总资源减去所有进程已分配资源之和,如果直接拿总资源当Available,那肯定能找到安全序列,但那是错的。再检查Need矩阵是不是Max - Allocation。最后检查安全性检查的循环里,工作向量Work有没有在进程完成后加上它的Allocation。这三步任何一步错,安全序列都会找不到或者找到错的。

5. 从能跑到能改:把这份骨架变成你自己的调度实验

cpp2.cpp跑通只是及格线,真正让这份资源值回票价的是改它。我一般会做三件事:加一组随机数据生成、加一个结果对比、加一个指标输出。

随机数据生成很简单,用rand()造到达时间和服务时间,跑一百组,看哪个算法的平均周转时间更优。这比手工输几组数据有说服力得多。代码大概长这样:

// 生成 n 个进程的随机到达时间和服务时间 void genRandom(PCB p[], int n) { srand(time(0)); for (int i = 0; i < n; i++) { p[i].arriveTime = rand() % 10; // 到达时间 0-9 p[i].serviceTime = rand() % 10 + 1; // 服务时间 1-10 p[i].runTime = 0; p[i].state = 'W'; } }

rand() % 10控制范围,+1避免服务时间为 0。跑对比时,把同一组随机数据分别喂给 FCFS 和 SJF,统计平均周转时间。你会发现 SJF 在大多数情况下更优,但偶尔会因为长进程饥饿而出现极端值,这正是实验报告里可以展开分析的点。

第二个改动是加一个甘特图式的输出。不用图形库,直接用字符打印执行顺序,比如| P1 | P2 | P3 |,每个进程下面标时间点。这样结果一目了然,报告里也好看。实现上就是在调度循环里每执行一个进程就打印一次进程名和当前时间。

第三个改动是给银行家算法加一个“请求资源”的交互。原始源码可能只做安全性检查,你可以加一个功能:输入某个进程的资源请求向量,先判断Request <= NeedRequest <= Available,然后试探性分配,再跑安全性检查,安全就正式分配,不安全就回滚。这个流程走一遍,银行家算法的理解就到位了。

最后说个习惯。我每次改完这类课程源码,都会把原始文件备份成cpp2_orig.cpp,改动的版本另存。因为课程实验经常要求交原始代码加修改说明,备份能省很多事。另外,实验报告里的结果截图最好在改之前和改之后各存一份,对比着写分析,比空谈理论有分量。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询