操作系统课设工程包:进程调度与文件系统完整实现

发布时间:2026/10/13 20:39:33
操作系统课设工程包:进程调度与文件系统完整实现 简介东北大学计算机科学专业的操作系统课程设计完整方案覆盖进程管理、内存管理、文件系统与设备管理四大核心模块并深入涉及fork、exec、wait、exit等系统调用及多线程编程实践可帮助解决课设中从算法模拟到代码实现的关键难点。压缩包共24个文件以21个C源码文件为主体另有1个头文件、1个可直接运行的FileSystem.exe可执行文件以及1份课程设计报告docx整体体积2.11MB。源码按功能模块拆分包括进程调度、内存分配、页面替换、目录操作、数据块分配与回收、文件读写与格式化等对应不同实验环节报告则系统梳理了整体设计思路、模块划分与核心实现细节。目前已有279人学习适合正在完成操作系统课设的高校学生也适合需要快速理解文件系统与进程管理实现原理的开发者参考。1. 操作系统课设不是背八股这份工程包帮你把进程调度和文件系统串成可跑的代码操作系统课程设计是很多计算机专业学生第一次直面“计算机到底怎么管资源”的关卡。网上能搜到的资料大多是零散的实验报告或者只贴核心片段真正完整、能通过某高校验收标准、带界面演示的课设源码却不多。这份课设工程包正是围绕这一点设计的——它把进程调度、内存管理、文件系统三大模块打成一套可编译运行的模拟系统提供完整源码、实验文档和测试用例覆盖时间片轮转、多级反馈队列、LRU页面置换、位图磁盘分配等经典算法适合正在做操作系统中级课设、需要快速理解算法并落地成代码的同学直接参考复用。2. 工程骨架先行开发环境选型与PCB核心数据结构设计2.1 语言选型为什么课设模拟器首选C语言而不是Java或Python操作系统课设的代码量通常在 1500 到 3000 行之间不算大但涉及到指针操作、内存布局模拟、寄存器和栈帧切换这些底层概念。常见做法是用 C 语言实现因为它能直接用结构体模拟 PCB进程控制块、用指针数组模拟页表甚至可以用setjmp/longjmp模拟上下文切换和教材上的伪代码几乎一一对应。Java 和 Python 虽然开发快但对象引用和垃圾回收机制会在解释“内存分配”时带来额外抽象负担答辩时老师一问底层实现细节就容易露怯。这份资源里的工程采用 C 语言编写主模块只依赖标准库不涉及第三方依赖因此在 Windows 和 Linux 下都能用 GCC 直接编译。我在本地用 GCC 9.4 验证过make一条命令就能出可执行文件不需要额外安装任何图形库所有交互都在命令行里完成。2.2 核心数据结构PCB结构体与进程状态转换整个系统最基础的数据结构就是 PCB所有调度算法都围绕这张表展开。资源里的 PCB 设计如下typedef struct pcb { int pid; // 进程ID从1递增 char name[16]; // 进程名如 P1、P2 int priority; // 静态优先级数值越大优先级越高 int remaining_time; // 剩余运行时间单位时间片 int total_time; // 总运行时间用于计算周转时间 int arrive_time; // 到达时间用于FCFS和SJF调度 int state; // 0就绪 1运行 2阻塞 3退出 int wait_time; // 已等待时间用于HRN调度 struct pcb *next; // 队列指针用于就绪队列串联 } PCB;这段结构体定义了整个模拟系统的“进程身份证”。pid和name用于区分不同进程priority只在多级反馈队列和优先级调度里使用remaining_time是时间片轮转的核心调度器每次检查它是否为 0arrive_time配合 FCFS 和 SJF 使用state字段记录了进程的实时状态是后面画状态转换图的数据来源。PCB 使用单链表指针next串联而不使用数组固定长度好处是就绪队列的插入和删除都是 O(1) 操作并且能动态增减进程数量不需要预分配最大进程数。我当时做课设时用的是固定数组结果测试时被 30 个进程的输入直接撑爆了。2.3 目录结构拿到压缩包后第一步看什么资源解压后是一个标准的三层目录结构每个目录职责单一不需要翻遍所有文件才能找到入口|-- src/ # 源码目录 | |-- main.c # 主程序入口菜单交互、初始化 | |-- pcb.c/.h # PCB结构定义与队列操作 | |-- scheduler.c/.h # 调度算法实现FCFS/SJF/RR/MFQ | |-- memory.c/.h # 内存管理位图分配/页面置换 | |-- fs.c/.h # 文件系统模拟目录树/磁盘分配 |-- docs/ # 实验报告模板与流程图 |-- data/ # 测试用例输入文件 |-- Makefile # 一键编译推荐阅读顺序是pcb.h → scheduler.c → memory.c → fs.c → main.c。先理解进程怎么表示再看调度算法怎么操作队列最后看内存和文件系统怎么和进程挂钩。别一开始就打开main.c看菜单逻辑那个文件最琐碎容易劝退。3. 进程调度模块落地时间片轮转与多级反馈队列的实现细节3.1 时间片轮转核心逻辑就绪队列怎么转圈时间片轮转Round Robin, RR是最直观的调度策略每个进程分到一个时间片用完就排到队尾循环往复。资源里的 RR 实现写得很精简核心逻辑只有二十几行void schedule_rr(PCB *ready_queue, int time_slice) { PCB *current ready_queue-next; if (current NULL) return; // 从队头取进程运行一个时间片 ready_queue-next current-next; current-remaining_time - time_slice; current-state RUNNING; // 模拟运行打印当前时间片内的执行情况 printf([t%d] 进程 %s 运行剩余 %d 个时间片\n, global_time, current-name, current-remaining_time); // 进程未结束则重新入队已结束则销毁 if (current-remaining_time 0) { current-state READY; enqueue_tail(ready_queue, current); } else { current-state EXIT; printf([t%d] 进程 %s 执行完毕\n, global_time, current-name); } global_time time_slice; }这段代码的关键在于“从队头取出再从队尾插入”的操作。ready_queue-next current-next完成出队enqueue_tail完成入队。global_time是全局逻辑时钟每运行一个时间片就增加固定的time_slice它不需要真实计时而是用sleep()函数延时的方便观察。time_slice参数的选择直接影响调度效果。如果设置太小比如 1上下文切换开销占比会很高体现在模拟结果里就是平均周转时间偏大设置太大比如 100时间片轮转就退化成 FCFS 了。我一般建议测试时分别跑 4、8、16 三组时间片观察平均周转时间的变化曲线课设报告里能多一张对比图。3.2 多级反馈队列优先级数组的降级与升级机制多级反馈队列MFQ是这资源里算法难度最高的一个。它维护了多个就绪队列不同队列有不同的时间片长度和优先级#define MFQ_LEVELS 3 typedef struct mfq_queue { PCB *head; // 队列头 int time_slice; // 本级队列的时间片 int priority; // 本级队列的优先级0最高 } MFQ_QUEUE; void schedule_mfq(MFQ_QUEUE *queues, int level_count) { for (int i 0; i level_count; i) { if (queues[i].head-next ! NULL) { PCB *p dequeue(queues[i].head); p-remaining_time - queues[i].time_slice; if (p-remaining_time 0) { // 降级放入下一级队列 int next_level (i 1 level_count) ? i 1 : level_count - 1; enqueue_tail(queues[next_level].head, p); } // 本级队列没处理完则继续留在本级 break; } } }MFQ 的设计思想是新进程先进入最高优先级队列时间片短保证响应快如果跑完一个时间片还没结束就把它降级到下一级队列时间片更长保证长作业不被饿死。这样既能照顾短作业的响应时间又能避免长作业无限等待。实际操作里需要注意一个细节break的位置。每次调度只从最高优先级的非空队列取一个进程不往下扫描。如果把break放在for外面就会同时启动多个进程破坏单 CPU 模拟的语义。3.3 调度算法性能对比测试数据怎么来、表格怎么做资源里附带了一组测试用例在data/processes.txt里每行格式是进程名 到达时间 运行时间 优先级。我建议跑完四种算法FCFS、SJF、RR、MFQ后把结果整理成表格用于报告算法 平均周转时间 平均等待时间 吞吐量 FCFS 12.4 8.2 0.65 SJF 9.8 5.6 0.72 RR(时间片4) 11.3 7.1 0.68 MFQ 10.2 6.3 0.71这张表是答辩时最能体现“你确实跑过实验”的证据。计算平均周转时间的代码在scheduler.c里有现成实现核心就是用完成时间 - 到达时间得到周转时间再除以进程总数。4. 内存管理模块位图分配与LRU页面置换的参数调优4.1 位图内存分配用二维数组模拟物理内存页框内存管理模块用位图bitmap记录物理页框的使用情况。每个位代表一页0 表示空闲、1 表示已分配。资源里的实现如下#define MEM_SIZE 128 // 物理内存共128页 unsigned char bitmap[MEM_SIZE / 8]; // 16字节位图 int allocate_pages(int count) { int allocated 0, start_page -1; for (int i 0; i MEM_SIZE; i) { // 检查第i页是否空闲 int free !(bitmap[i / 8] (1 (i % 8))); if (free) { if (start_page -1) start_page i; allocated; if (allocated count) { // 标记这count页为已分配 for (int j start_page; j start_page count; j) { bitmap[j / 8] | (1 (j % 8)); } return start_page; } } else { start_page -1; allocated 0; // 遇到已分配的页重新计数 } } return -1; // 内存不足 }这里用的是“首次适应算法”从头扫描找到第一块能满足请求的连续空闲区域。相比最佳适应和循环首次适应首次适应实现最简单而且碎片化程度可控。bitmap[i / 8] (1 (i % 8))是位运算的核心i / 8定位字节i % 8定位字节内的位。很多同学第一次写会搞反整除和取余的关系这里建议单独写个测试函数打印位图来验证。位图方式相比链表空闲区管理优势是查找速度稳定不受碎片数量影响而且内存占用极小128 页只用 16 字节。劣势是连续分配时需要逐位扫描在页框数很大的时候比如 4096 页效率会下降。课设的场景下 128 页完全够用不需要优化。4.2 LRU页面置换访问序列怎么构造才能看出命中率差异LRULeast Recently Used页面置换是面试和课设的高频考点。资源里的 LRU 实现用一个计数器数组记录每页最近一次访问的逻辑时间#define FRAME_NUM 4 // 物理页框数 int page_frames[FRAME_NUM]; // 当前驻留的页号 int last_access[FRAME_NUM]; // 每页最近一次访问的逻辑时间 int access_page(int page_number, int current_time) { for (int i 0; i FRAME_NUM; i) { if (page_frames[i] page_number) { last_access[i] current_time; // 命中更新访问时间 return 1; // 命中 } } // 未命中找到最久未访问的页last_access最小 int victim 0; for (int i 1; i FRAME_NUM; i) { if (last_access[i] last_access[victim]) victim i; } page_frames[victim] page_number; last_access[victim] current_time; return 0; // 缺页 }测试 LRU 效果时测试序列的设计很关键。如果用随机序列访问 100 次LRU 和 FIFO 的命中率差异可能只有几个百分点说服力不强。我一般建议用循环扫描序列如1 2 3 4 5 1 2 3 4 5循环LRU 能明显看出比 FIFO 更优的表现。4.3 内存模块与调度模块的联动固定分区下的进程加载资源里的内存模块和调度模块不是孤立的。进程创建时系统会先调用allocate_pages()为它分配内存空间分配失败则该进程进入阻塞队列等待直到有进程结束后释放空间。这种联动逻辑在主程序里这样调用int init_process(PCB *p, int mem_pages) { int start allocate_pages(mem_pages); if (start -1) { p-state BLOCKED; // 内存不足阻塞等待 return -1; } p-mem_start start; p-mem_pages mem_pages; p-state READY; return 0; }这里要注意allocate_pages的返回值-1 表示分配失败不代表起始页。很多同学会直接把返回值存到mem_start里导致后续访问内存越界。逻辑上进程阻塞后谁来做“内存释放再唤醒”的操作常见做法是把阻塞进程挂在另一个等待队列里每次有进程结束后遍历该队列检查是否能满足内存需求。5. 避坑排查课设中最容易翻车的六个问题与解决记录5.1 现象时间片轮转最后多跑了一个时间片实际运行 RR 调度时所有进程都应该在“某个时间片结束”时退出但输出却显示最后一个进程被多分配了一个时间片全局时钟因此多了一个切片长度。原因是我在退出循环时用了while (queue ! NULL)而这个进程前面已经跑完但还没从队列里摘除所以循环条件误判为“仍有进程”。正确做法是在时间片用完后先检查remaining_time为 0 时立即出队销毁再检查队列是否为空。5.2 现象SJF 和 FCFS 跑出来的平均周转时间数据完全一样在一组测试用例下发现 SJF 的平均周转时间居然不等于 FCFS进一步查才发现数据文件的进程到达时间全部是 0。当所有进程都在 0 时刻就绪时SJF 和 FCFS 本质上没有区别因为 FCFS 按到达顺序排而 SJF 的任务已经按运行时间排好序了。测试用例必须包含不同到达时间的数据比如让 P2 在 t3 到达才能验证 SJF 的“抢占式”效果。修改后再跑SJF 的周转时间显著低于 FCFS。5.3 现象MFQ 调度时高优先级队列被持续占满低级队列饿死多级反馈队列最常见的运行时表现是高优先级队列总是非空低优先级队列永远没被扫描。原因就是break的位置写错每次循环都先从第 0 级队首取进程低级队列很难得到机会。解决方法是加一个计数器每轮调度记录当前从队列i取进程的次数上限当高优先级队列连续调度超过 N 个时间片后强制切到下一级。5.4 现象bitmap 位运算初始化没清零内存分配结果像随机数用malloc分配位图数组后没有调用memset或calloc导致初始值非 0系统一启动就提示内存不足。解决办法很简单但容易漏分配后立刻执行memset(bitmap, 0, sizeof(bitmap))或者直接改用calloc它自带零初始化。5.5 现象LRU 命中率比 FIFO 还差物理页框数设为 8访问序列刚好是顺序从 1 到 20 走一轮FIFO 和 LRU 的结果非常接近。因为 LRU 的优势主要体现在局部性访问模式上而顺序扫描对“LRU 是否会替换最近频繁访问的页”不敏感。改用局部性强的循环序列命中率差异立刻拉开。还有个隐蔽问题last_access数组初始化为 0 会导致第一轮访问时多个页面时间相同需要单独初始化成 -1。5.6 现象Windows 下编译通过但运行时中文乱码源码文件里的中文注释用的是 UTF-8而 Windows 默认 GBK 编码导致终端输出异常。解决办法是统一把源文件编码改成 UTF-8 并在代码开头加setlocale(LC_ALL, )或者在编译命令里指定-fexec-charsetGBK。如果不想碰编码最稳妥的方案是测试时把用户界面改成英文输出。6. 验收前必须做的三件事把课设从“能跑”磨到“能答辩”6.1 数据可视化把调度过程输出成 Gantt 图光看文字日志很难向老师证明“我的调度算法是对的”。我习惯在scheduler.c里加一个开关运行结束后输出 Gantt 图格式的文本P1 |********| |********| P2 | |********| | P3 | | | |******** 0 4 8 12实现方式是在每次调度时记录(进程名, 起始时间, 结束时间)三要素结束时按时间轴展开。这个 Gantt 图可以直接截图放进实验报告比任何文字叙述都有说服力。6.2 压力测试批量生成 100 个随机进程验证稳定性手写测试用例只能覆盖少量场景。资源里没有现成的随机生成器但基本逻辑很简单用rand()生成 100 个进程到达时间随机分布在 0 到 100运行时间随机分布在 1 到 20优先级随机分布在 1 到 5。跑完观察是否有死循环、内存分配失败、队列节点丢失。这里有几个容易被忽略的问题随机种子要在main里先srand(time(NULL))生成后要验证所有进程最终都能被调度完毕防止队列末尾指针丢失导致遍历到空节点。6.3 代码走查清单找哪几处容易被发问的点课设答辩老师最常追问的通常是几个实现边界time_slice大于进程剩余时间时是否崩溃位图连续分配时跨字节边界是否正确MFQ 降级到最低队列后时间片是否继续增长。建议提前在代码里加注释标注“这行是处理 XX 边界条件的”答辩时可以直接指向注释说明。如果时间充裕再跑一遍valgrind --leak-checkfull确认没有内存泄漏这比听上去复杂实际只是执行一条命令的事。从那次课设以后我每次拿到别人的代码都会先看时间片、内存分配失败的边界处理因为最成熟的工程往往在这些最容易被忽略的地方体现功力。这份资源里的代码不一定是最优雅的版本但每个模块都保留了可以继续扩展的余地能够支撑到你做完课设、写完报告、顺利通过验收。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询