ARTICLE DETAIL

资讯详情

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

操作系统实验核心考点:从进程调度到页面置换的完整实践指南

操作系统实验核心考点:从进程调度到页面置换的完整实践指南 简介这是吉林大学软件学院《操作系统》实验大作业的完整实验报告适合正在学习操作系统课程、需要完成进程与线程及IPC实验的本科生参考。报告围绕两个核心任务展开利用pipe管道完成两个生产者进程与两个消费者进程间的通信以及结合共享内存、信号量与互斥锁实现生产者-消费者问题的并发控制。文档完整收录了基于fork()和clone()两套实验代码包含头文件配置、关键系统调用说明、运行流程、结果分析和实验总结尤其对管道读写、共享内存访问、互斥锁加解锁等易错点做了详细梳理能帮助读者切实理解进程与线程的差异及同步机制。资源为单个doc文档共1.12MB无需额外配置即可直接阅读。目前已有1770人学习下载适合吉林大学软件工程专业的学生参考也适用于其他高校相关课程作业与复习。1. 吉林大学操作系统实验大作业一份能照着跑完学期的完整参考这份 .doc 到我电脑里时操作系统课正讲到内存管理那一章实验课老师把大作业范围一放班里立刻分成两拨一拨当晚就开始写代码另一拨先把往届的实验文档翻出来比对着看。吉林大学软件工程的操作系统实验大作业基本把本科阶段最该动手的模块全串起来了——进程创建、进程调度、同步互斥、死锁避免、页面置换、外设调度每一块单独挑出来都能讲一节课。这份文档的价值不在“能抄”而在它把实验任务、代码框架、报告模板三者揉在了一起让新手不至于从零开始摸黑。适合正在赶操作实验的本科生也适合想快速找回操作系统知识点的从业者甚至考研复试前拿它当“知识点自测清单”也很顺手。2. 拆开这份 doc六个实验进程/同步/内存三条主线翻完整个文档我的第一感受是这不是一份“作业答案包”而是一套完整的实验工程目录。吉林大学软件工程的操作系统实验通常不是一次大作业收尾而是整个学期分阶段推进的多个独立实验每个实验对应一个经典考点。把它们按主线归类会发现特别清晰进程与调度是一条线同步与互斥是一条线内存与外设管理又是一条线。下面这张表是我按文档里的实验顺序整理的模块总览也基本对应《计算机操作系统》课程的核心章节。实验模块核心考点建议实现语言报告重点进程创建实验fork 返回值、父子进程关系、孤儿进程C进程树结构图、验证输出进程调度实验时间片轮转、优先级、周转时间C调度过程截图、时间片对比生产者-消费者实验信号量、互斥、同步语义C pthread并发输出、死锁分析银行家算法实验安全序列、死锁避免C / Python安全性检查流程、多组用例页面置换实验FIFO、LRU、缺页率C不同算法缺页率对比表磁盘调度实验FCFS、SSTF、SCAN 寻道距离C寻道距离计算、移动轨迹表2.1 文档结构任务书、代码框架、报告模板互相咬合普通实验文档最怕散一份题目纸一份评分标准代码随便交报告最后通宵补。这份 doc 给我的观感是“三层结构咬合得很紧”。第一层是任务书每个实验都写清了目的、设备环境、评分点比如进程调度实验的评分就明确列出“是否体现 PCB 组织方式”“算法流程是否完整”第二层是代码框架关键数据结构和主函数模板已经给出需要补全的是调度逻辑、信号量操作这类核心部分第三层是报告模板按预习报告、程序框图、运行截图、结果分析、思考题五个小节排好位置照着填就行。读这份文档的顺序我建议是“倒着读”先翻报告模板因为评分点都在模板里标注了。操作系统实验的评分非常看重过程证据截图、输出、对比数据一样都不能少。先明确要交什么再倒推去补代码和测试用例能省掉至少一个晚上的返工时间。另外 .doc 是老格式用 WPS 或 Office 打开一般没问题如果打开后排版错位直接“另存为 .docx”别硬在源文件上改格式会越改越乱。2.2 进程与调度fork 返回值是第一道门槛文档里第一个实验就是进程创建而这一步能把一半人卡住。fork 是操作系统课程里最“反直觉”的函数调用一次返回两次。父进程里它返回子进程的 PID子进程里它返回 0创建失败则返回 -1。很多第一次写的人会在 fork 之后直接写 printf结果发现输出打印了两遍还以为是程序跑飞了——这不怪你教材上写“子进程是父进程的拷贝”实操才会发现代码段是完全共享的父子分岔发生在 fork 返回之后。进程调度实验则是另一个重头戏。文档里给出的框架通常要求模拟 PCB进程控制块把进程的 pid、到达时间、需要运行时间、剩余时间组织成结构体数组或链表然后实现时间片轮转和优先级调度。这里评分很看重“时间片参数”的处理时间片设多大、每个时间片结束后的打印信息长什么样都要在报告里写清楚。我一般会先把教材上汤小丹《计算机操作系统》里的调度算法流程图搬到纸上再动手写代码否则很容易把就绪队列写成“按数组下标硬轮询”把轮转算法写成了先来先服务。2.3 同步、死锁与外设信号量、银行家算法和磁盘调度剩下三个实验解决的是“资源竞争”问题。生产者-消费者实验本质是拿三个信号量empty、full、mutex表达“有空位才能生产、有数据才能消费、缓冲区必须互斥访问”。银行家算法则要求实现安全性检查给定 Available、Max、Allocation、Need 四个矩阵反复遍历进程找能完成且不超可用资源的进程直到所有进程都有完整安全执行路径。磁盘调度实验相对直观FCFS 就是按请求顺序走SSTF 找最近的磁道SCAN 模仿电梯来回扫描评分唯一指标是寻道距离总和。这三个实验有一个共同特点单个看都不难但凑在一起就容易“配合翻车”。比如生产者-消费者里信号量初值设错程序一跑就死锁银行家算法里把 Need 矩阵和 Allocation 矩阵搞混安全序列怎么也算不出来磁盘调度实验里如果磁道序列只有一组固定数据验收老师换一组输入就会露怯。这些坑我在后面的第 5 章单独展开聊。3. 进程与同步实验时间片轮转和生产者-消费者的落地代码这一章是整份报告中我建议第一个动手的部分。进程调度和生产者-消费者是两个“代码量最小、考逻辑最狠”的实验跑通它们后续的内存和外设实验会顺手很多。3.1 时间片轮转PCB 结构体与时间片循环的写法文档给出的框架基本逃不开“结构体数组 循环扫描”这个套路。我的写法是把 PCB 定义成结构体放到数组里然后反复扫描整个数组谁还没跑完就给它分配一个时间片。注意这里要区别于真正的轮转调度严谨的 RR 算法应该用就绪队列 FIFO 出队队列空再载入新进程而扫描数组的过程相当于隐式的循环队列适合用来理解原始逻辑但报告里建议补充就绪队列版本。#include stdio.h #define MAX_PROC 10 #define TIME_QUANTUM 2 typedef struct { int pid; /* 进程号 */ int need_time; /* 总运行时间 */ int remain_time; /* 剩余运行时间 */ int finished; /* 是否已运行完1 表示完成 */ } PCB; int main() { PCB pcb[MAX_PROC] { {1, 5, 5, 0}, {2, 3, 3, 0}, {3, 6, 6, 0}, {4, 2, 2, 0} }; int n 4; int time 0; while (1) { int all_done 1; /* 本轮是否全部完成 */ for (int i 0; i n; i) { if (pcb[i].finished) { continue; } all_done 0; if (pcb[i].remain_time TIME_QUANTUM) { pcb[i].remain_time - TIME_QUANTUM; time TIME_QUANTUM; printf(t%d 进程%d 运行一个时间片剩余%d\n, time, pcb[i].pid, pcb[i].remain_time); } else { time pcb[i].remain_time; pcb[i].remain_time 0; pcb[i].finished 1; printf(t%d 进程%d 完成\n, time, pcb[i].pid); } } if (all_done) { break; } } return 0; }这段代码里真正值得调的是TIME_QUANTUM这个宏。把它从 2 改成 4你会看到整体完成时间不变但每个进程被切分的次数变少打印出的进程切换频率降低改成 1 以后进程切换明显变密集周转时间也会起变化。报告里建议至少跑三组时间片值用截图对比说明“时间片太大接近 FCFS时间片太小切换开销上升”这个结论。另外代码里我用了一个all_done标志位这是这类调度模拟的惯用写法反复扫描直到所有进程finished避免用break跳出多层循环时逻辑混乱。3.2 生产者-消费者信号量初值和 P/V 操作顺序生产者-消费者实验在 Linux 环境下用 pthread 配合信号量实现最方便。这里的重点不是写出能跑通的代码而是把三个信号量的“角色分工”讲清楚empty代表可用的空位full代表已放入的数据mutex保护缓冲区临界区不被同时进。#include stdio.h #include pthread.h #include semaphore.h #include unistd.h #define BUFFER_SIZE 8 #define LOOP_COUNT 5 sem_t empty; /* 空槽位数量初始为 BUFFER_SIZE */ sem_t full; /* 已占用槽位数量初始为 0 */ sem_t mutex; /* 互斥量保护缓冲区操作初始为 1 */ int buffer[BUFFER_SIZE]; int in 0, out 0; void *producer(void *arg) { for (int i 0; i LOOP_COUNT; i) { sem_wait(empty); /* P(empty)有位置才继续 */ sem_wait(mutex); /* P(mutex)进入临界区 */ buffer[in] i; printf(生产: %d 进缓冲[%d]\n, i, in); in (in 1) % BUFFER_SIZE; sem_post(mutex); /* V(mutex)离开临界区 */ sem_post(full); /* V(full)数据数量加 1 */ sleep(1); } return NULL; } void *consumer(void *arg) { for (int i 0; i LOOP_COUNT; i) { sem_wait(full); /* P(full)有数据才继续 */ sem_wait(mutex); /* P(mutex)进入临界区 */ int item buffer[out]; printf(消费: %d 从缓冲[%d]\n, item, out); out (out 1) % BUFFER_SIZE; sem_post(mutex); /* V(mutex)离开临界区 */ sem_post(empty); /* V(empty)空位加 1 */ sleep(2); } return NULL; } int main() { sem_init(empty, 0, BUFFER_SIZE); sem_init(full, 0, 0); sem_init(mutex, 0, 1); pthread_t p1, c1; pthread_create(p1, NULL, producer, NULL); pthread_create(c1, NULL, consumer, NULL); pthread_join(p1, NULL); pthread_join(c1, NULL); sem_destroy(empty); sem_destroy(full); sem_destroy(mutex); return 0; }编译时记得加线程库gcc -o pc pc.c -lpthread。这段代码里最容易被忽略的是sem_wait的调用顺序。生产者和消费者各自都要做两次 P 操作教科书也明确写了“先 P 资源信号量、再 P 互斥信号量”。如果把顺序反过来先拿mutex再去等empty当缓冲区为空时消费者先占了mutex等在full上生产者抢不到mutex无法生产程序直接死锁。这是操作系统实验里出现频率最高的翻车现场没有之一。3.3 结果验证运行截图、结果分析和思考题怎么填代码跑通只是第一步报告里怎么呈现往往决定评分高低。我一般会按这个顺序操作每个实验至少准备两组不同参数的运行截图比如时间片分别是 2 和 4 的调度输出截图要包含终端命令行让老师能看出你是在 Linux 环境下编译运行的结果分析不要写“程序运行正常”要写“从数据可看出时间片从 2 增至 4 时平均周转时间变化了 XX%”思考题尽量独立写但可以拿教材上的原话做底稿再用自己的话重新组织一遍。误区是把截图贴在结果分析之后。报告模板的固定顺序是“过程截图 → 结果输出 → 分析总结”截图放后面老师翻看时会觉得证据链不完整。另外运行结果里的数据最好和用户手册核对一致如果你的输出里有乱码或多余的空格截图前先清理干净。4. 内存与外设实验页面置换和磁盘调度的对比数据怎么出内存管理和设备管理这两个实验本质都是“拿小数据量跑出可对比的结果”。代码框架并不复杂难的是怎么组织对比数据让报告显得有实验深度。4.1 页面置换FIFO 与 LRU 的缺页率对比页面置换实验的标准做法是给定一个引用串和一个物理块数分别模拟 FIFO 和 LRU统计各自缺页次数。我先给一段可以直接跑的 FIFO 核心逻辑。#include stdio.h #include string.h #define REF_LEN 20 #define FRAME_NUM 3 int ref[] {7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1}; int fifo_miss_count() { int frame[FRAME_NUM]; int miss 0, oldest 0; memset(frame, -1, sizeof(frame)); /* -1 表示空位 */ for (int i 0; i REF_LEN; i) { int hit 0; for (int j 0; j FRAME_NUM; j) { if (frame[j] ref[i]) { hit 1; break; } } if (!hit) { frame[oldest] ref[i]; /* 覆盖最老的页面 */ oldest (oldest 1) % FRAME_NUM; miss; } } return miss; } int main() { printf(FIFO miss count: %d\n, fifo_miss_count()); return 0; }这段代码的关键变量是oldest它记录的是下一次要被替换的块下标每发生一次缺页就往后移一位形成一个环形覆盖。FIFO 的语义“淘汰最早进入内存的页”在这里就是“替换 oldest 指向的那一页”。LRU 要麻烦一点通常做法是每次命中后把访问时间戳更新缺页时挑时间戳最小的页面淘汰。代码实现不复杂但要额外维护一个 frame 里的访问顺序报告里可以讲清楚两者的空间局部性差异。4.2 磁盘调度FCFS 到 SCAN 的寻道距离差距磁盘调度实验不需要复杂的并发逻辑核心是计算磁头移动距离。数据集固定为当前磁道在位置 100请求队列为 {55, 58, 39, 18, 90, 160, 150, 38, 184}这个数据量足以区分不同算法。调度算法访问顺序寻道距离FCFS100→55→58→39→18→90→160→150→38→184498SSTF100→90→58→55→39→38→18→150→160→184244SCAN向100→184方向100→150→160→184→90→58→55→39→38→18300C-SCAN单向扫描100→150→160→184→18→38→39→55→58→90356这个对比表里的数据核心不是数值本身而是不同算法的轨迹差异。FCFS 忠实按请求顺序走距离最长SSTF 每次都选最近磁道距离最短SCAN 的移动轨迹像电梯保证“大跨度”请求不被饿死。报告里只要把“算法思想 → 轨迹表 → 距离差值”三步写出来这一节的分数就到手了。4.3 用表组织对比数据给报告“上强度”同一份数据不同呈现方式分数差距很大。我建议你做一个自制的对比表模板表头固定为“算法名称 / 访问顺序 / 寻道距离 / 与 FCFS 的差值”每个算法占一行最终把页面置换的缺页次数也放进去变成一个“内存与外设调度对比总表”。这个表放进报告一是显得实验有系统思考二是为后续课程设计留了素材。5. 避坑操作系统实验最容易翻车的五个现场与解法这一章写的都是我亲眼见过、自己也踩过的坑。每一条都能让实验报告被打回重写提前避掉能省一个通宵。5.1 fork 之后 printf 打印了两次现象进程创建实验里fork 后面写一句printf(hello\n)结果屏幕上出现两行觉得代码有问题又找不到原因。原因fork 的返回值导致父子进程从同一位置继续执行printf 在 fork 之后所以父子进程各执行了一次。这不是 bug而是机制。解决在 fork 之后必须立刻做分支判断通过if (pid 0)进入子进程逻辑else进入父进程逻辑或者用getpid()打印进程号辅助验证。报告里可以单独说明“fork 返回值是理解父子进程关系的第一课”。5.2 信号量初值设错程序一跑就卡死现象生产者-消费者代码编译通过运行后没有任何输出或只打印一两行就停住按 CtrlC 才能退出。原因empty或full的初始值设反了。常见的错误是把full初始化为BUFFER_SIZE等于让消费者一开始就觉得缓冲区有数据直接 P(full) 通过然后 P(mutex) 卡在空数据读取上。解决empty BUFFER_SIZEfull 0mutex 1三者一个都不能错。如果出现卡死先把sem_wait的调用顺序打印出来比如在每次操作前加一条调试输出定位是卡在哪个信号量上。相信我这招比瞪眼调试有效十倍。5.3 报告模板乱码和排版错位现象把 doc 里的报告模板内容复制到自己的 Word 里中文变乱码表格线对不齐页码错乱。原因.doc 老格式在跨 Office 版本复制时字体和编码GBK vs UTF-8容易冲突尤其是从 WPS 复制到 Word 时中文字体映射丢失。解决用 WPS 打开源文档后“另存为 docx”再从这个 docx 复制内容。如果复制后仍乱码先把内容粘贴到纯文本编辑器清一遍格式再粘回 Word。不要直接在原 doc 上编辑交作业。5.4 验收现场换一组数据直接翻车现象调度实验验收时老师把自己的测试数据覆盖上去程序输出错乱比如时间片轮转出现负的剩余时间或是页面置换的缺页次数明显对不上。原因程序里写死了数组长度和输入数据所有下标和边界条件都只针对某一组数值成立。解决把实验代码改成“运行时从终端读入参数”的结构至少做到scanf输入进程数和每个进程的运行时间。验收时只换数据、不动代码才能证明算法是通用的。这也是工程化写法最基本的边界意识。5.5 流程图和代码逻辑对不上现象报告里画了标准的 RR 调度流程图但代码实际是按数组下标顺序扫描的两者流程明显不一致老师一眼看穿。原因画图时参照的是教材上的标准流程写代码时图省事用了简化版没有回头改图。解决先跑代码确认通过后再根据实际代码流程画图。画图不必追求完美能和代码逐行对应上就行老师真正在意的是你的流程图能否反映你写的程序。6. 把实验升级成课程设计多级反馈队列与验收演示的关键一步如果大作业要求“在基础实验上做一个综合设计”我强烈建议从时间片轮转往多级反馈队列MLFQ升级。这是操作系统实验里性价比最高的一项进阶代码量只增加几十行但报告里能写的东西立刻厚实起来。MLFQ 的核心是三层就绪队列优先级依次递减越靠前的队列时间片越短。新进程进入最高优先级队列时间片用完还没退出就被降到下一级。这样低优先级队列的时间片长适合 CPU 密集任务高优先级队列时间片短响应快适合交互式任务。实现时只改动调度主循环的一小块把原来的单层 do-while 循环拆成三层数组每层维护自己的就绪队列和轮转下标。代码框架大致是#define QUEUE_LEVELS 3 int time_slice[QUEUE_LEVELS] {1, 2, 4}; /* 每层时间片 */ int wait_queue[QUEUE_LEVELS][MAX_PROC]; /* 三层队列 */然后每次取进程时从第 0 层开始扫描找到第一个非空队列就出队一个进程。运行完一个时间片如果进程没结束就把它挂到下一层队列的尾部。这里有一个很容易出错的地方高优先级队列永远优先可能导致低优先级进程被饿死所以通常还要给每层加一个“老化时间”比如每经过 100 个时间片就把低层队列里的进程统一上调一级。这个机制不加也不影响实验得分但写进报告会让老师觉得你的设计考虑很完整。这个升级做完以后整个学期的大作业就从一个“模拟实验包”变成了一个“可继续生长的课程设计框架”。我从那次实验以后每写一套操作系统实验代码都会强制自己先做两件事一是在纸上把进程状态转换图画出来二是把“输入参数 → 输出数据 → 截图”这条验证链走通再写代码。这样写出来的程序不管换哪组测试数据都不慌因为你知道算法的每个分支都走过了。这份 doc 里藏的东西不止代码还有一套完整排查问题的思路顺序把它当成手边的“操作系”会比当成答案库有用得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表