
简介这份资源是北京交通大学操作系统课程的实验答案与报告合集面向正在修读操作系统实验课、需要参考实现思路与报告写法的本科生也可供自学者对照练习。压缩包共41个文件约73KB以26个C语言源文件为主另有5个C文件、4个Markdown实验报告、2个头文件及汇编、文本等辅助文件覆盖进程调度、内存管理、文件系统、死锁与权限控制等实验模块。内容包含各实验的代码实现与配套报告如进程调度模拟、页面置换算法、管道与FIFO通信、文件系统模拟及银行家算法等报告部分记录了实验设计、实现过程与结果分析便于理解操作系统核心机制并对照排错。目前已有202人学习适合需要快速搭建实验框架、梳理报告结构或查漏补缺的同学参考。1. 操作系统实验答案和报告从“能跑”到“能讲清楚”的交付路径北京交通大学操作系统实验答案和报告.zip 这个标题背后其实是一类非常典型的诉求实验代码能跑通但报告写不出来或者报告写完了答辩时被问两句就露馅。操作系统实验通常覆盖进程调度、内存管理、文件系统、同步互斥这几大块每块都有对应的代码框架和观测指标。真正卡住大多数人的不是写代码而是不知道实验到底在验证什么、数据怎么解读、报告里该放什么图什么表。这篇笔记面向正在做操作系统实验的本科生和自学者把从环境搭建、代码调试到报告撰写的完整路径拆开讲重点放在“怎么让实验结果可复现、可解释、可答辩”上。如果你手里已经有一份答案压缩包但不确定里面的实现是否合理或者想自己从头做一遍下面的内容可以直接对照使用。2. 实验环境搭建编译内核模块和用户态程序的两种路径操作系统实验一般分两类一类是修改或扩展内核模块比如添加系统调用、实现调度算法另一类是在用户态模拟操作系统行为比如用多线程模拟进程调度、用共享内存模拟页面置换。北交大的实验体系里这两类都有涉及具体取决于课程阶段。不管哪一类环境搭建都是第一道坎而且不同路径的坑完全不一样。2.1 内核模块路径编译环境与版本匹配如果实验要求修改内核源码或加载内核模块第一步是确认当前运行内核版本和实验指导书要求的是否一致。常见做法是用uname -r查看然后安装对应的内核头文件。# 查看当前内核版本 uname -r # 安装当前内核对应的头文件Ubuntu/Debian系 sudo apt install linux-headers-$(uname -r) # 如果实验要求特定版本比如 5.15.0-xx-generic # 需要先确认该版本是否在 apt 源中可用 apt-cache search linux-headers-5.15.0逻辑说明内核模块编译依赖内核头文件中的符号表和宏定义版本不匹配会导致insmod时报Invalid module format或Unknown symbol。参数上uname -r输出的完整版本号必须和/lib/modules/下的目录名一致否则 Makefile 里的KERNELDIR指向就会错。如果实验指导书指定了内核版本而当前系统不是常见做法是安装对应版本的linux-image和linux-headers包然后更新 GRUB 启动项重启后选择该版本进入。这一步的坑在于有些实验环境是虚拟机模板内核版本被锁定随意升级可能导致实验框架无法加载。我一般会先备份当前内核再操作。2.2 用户态模拟路径多线程与共享内存的编译选项用户态实验通常用 C 或 C 写涉及pthread、shmget、semaphore等。编译时必须加-lpthread和-lrt否则链接阶段会报未定义引用。// 示例用 pthread 模拟两个进程竞争临界区 #include pthread.h #include stdio.h #include semaphore.h sem_t mutex; int shared_counter 0; void* worker(void* arg) { for (int i 0; i 100000; i) { sem_wait(mutex); // 进入临界区 shared_counter; // 非原子操作必须保护 sem_post(mutex); // 离开临界区 } return NULL; } int main() { sem_init(mutex, 0, 1); // 初始化信号量初值1 pthread_t t1, t2; pthread_create(t1, NULL, worker, NULL); pthread_create(t2, NULL, worker, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf(counter %d\n, shared_counter); sem_destroy(mutex); return 0; }编译命令gcc -o race race.c -lpthread -lrt逻辑说明sem_wait和sem_post包裹的代码段是临界区shared_counter在汇编层面是读-改-写三步不加保护时两个线程可能同时读到旧值导致最终结果小于 200000。参数上sem_init的第二个参数为 0 表示线程间共享非 0 表示进程间共享第三个参数是信号量初值互斥场景固定为 1。这个实验的观测点是不加信号量时结果随机偏小加了之后稳定为 200000。报告里需要放多次运行的对比数据而不是只跑一次就下结论。2.3 实验框架的目录结构与 Makefile 适配北交大的实验通常提供一个框架目录里面包含Makefile、test.c、student.c等文件。学生只需要在指定文件中填充函数体。常见问题是框架的 Makefile 写死了编译器路径或内核路径换环境后直接make会报错。# 典型的内核模块 Makefile obj-m mymodule.o KERNELDIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: make -C $(KERNELDIR) M$(PWD) modules clean: make -C $(KERNELDIR) M$(PWD) clean逻辑说明obj-m指定要编译的模块目标文件KERNELDIR指向内核构建目录。如果实验指导书要求的内核版本和当前不一致需要手动把KERNELDIR改成/lib/modules/5.15.0-xx-generic/build这样的绝对路径。参数上M$(PWD)告诉内核构建系统到当前目录找模块源码。提示如果make报No such file or directory指向build目录先确认linux-headers-$(uname -r)是否安装成功再检查/lib/modules/$(uname -r)/build是否是一个有效软链接。3. 核心实验模块的实现与调试调度、内存、文件系统操作系统实验的答案和报告之所以难写是因为每个模块的验证方式不同。调度算法看周转时间和响应比内存管理看缺页率和置换次数文件系统看目录树和 inode 分配。下面按模块拆开讲实现要点和调试方法。3.1 进程调度算法从 FCFS 到多级反馈队列的代码骨架调度实验通常要求实现几种算法并对比指标。以最高响应比优先HRRN为例核心是每次选择响应比最高的进程执行。typedef struct { int pid; int arrive_time; int burst_time; int start_time; int finish_time; } Process; // 计算响应比 (等待时间 服务时间) / 服务时间 double response_ratio(Process p, int current_time) { int wait current_time - p.arrive_time; return (double)(wait p.burst_time) / p.burst_time; } // 选择响应比最高的进程 int select_hrrn(Process procs[], int n, int current_time, int finished[]) { int best -1; double max_ratio -1.0; for (int i 0; i n; i) { if (finished[i]) continue; if (procs[i].arrive_time current_time) continue; double r response_ratio(procs[i], current_time); if (r max_ratio) { max_ratio r; best i; } } return best; }逻辑说明response_ratio的分母是服务时间分子是等待时间加服务时间。等待时间越长、服务时间越短响应比越高。参数上current_time是当前调度时刻finished数组标记已完成的进程。调试时常见错误是忘记判断arrive_time current_time导致选中还没到达的进程。报告里需要放一张表列出每个进程的到达时间、服务时间、开始时间、完成时间、周转时间、带权周转时间。对比 FCFS、SJF、HRRN 三组数据说明 HRRN 在长短作业混合场景下的优势。3.2 页面置换算法FIFO、LRU、Clock 的命中率对比内存管理实验一般要求模拟页面置换统计缺页次数和缺页率。LRU 的实现可以用链表或计数器Clock 算法需要维护访问位和指针。# LRU 页面置换模拟 def lru(pages, capacity): memory [] page_faults 0 for page in pages: if page in memory: memory.remove(page) # 命中移到最近使用位置 memory.append(page) else: page_faults 1 if len(memory) capacity: memory.pop(0) # 淘汰最久未使用 memory.append(page) return page_faults # 测试序列 pages [7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1] print(LRU faults:, lru(pages, 3))逻辑说明memory列表头部是最久未使用尾部是最近使用。命中时先remove再append保持顺序。缺页且容量满时pop(0)淘汰头部。参数上capacity是物理块数通常取 3 或 4。调试时注意Python 的list.remove是值删除如果页面号重复出现要确认删的是正确位置。报告里需要对比 FIFO、LRU、Clock 在相同页面序列下的缺页次数。常见结论是 LRU 优于 FIFO但 Clock 在实现开销和性能之间取得平衡。数据要跑多组序列不能只跑一组就写结论。3.3 文件系统模拟目录树与 inode 分配文件系统实验通常要求实现简单的目录树、文件创建删除、inode 分配。核心数据结构是超级块、inode 位图、数据块位图。#define BLOCK_SIZE 1024 #define MAX_INODES 128 #define MAX_BLOCKS 4096 typedef struct { int inode_bitmap[MAX_INODES]; // 0 空闲1 占用 int block_bitmap[MAX_BLOCKS]; int inode_count; int block_count; } SuperBlock; typedef struct { int inode_no; int file_size; int block_ptrs[12]; // 直接块指针 int is_dir; } Inode; // 分配一个空闲 inode int alloc_inode(SuperBlock* sb) { for (int i 0; i MAX_INODES; i) { if (sb-inode_bitmap[i] 0) { sb-inode_bitmap[i] 1; sb-inode_count; return i; } } return -1; // 无空闲 inode }逻辑说明inode_bitmap用数组模拟位图0 表示空闲1 表示占用。alloc_inode遍历找到第一个空闲项并标记。参数上MAX_INODES和MAX_BLOCKS根据实验指导书设定常见是 128 和 4096。调试时常见错误是忘记更新inode_count导致统计信息不一致。报告里需要画出目录树结构列出每个文件的 inode 号、大小、占用块号。如果实验要求支持多级目录还需要展示路径解析过程。4. 实验报告撰写数据、图表和结论的对应关系代码跑通只是第一步报告才是最终交付物。很多同学代码没问题但报告被扣分原因通常是数据不完整、图表和结论对不上、缺少误差分析。下面按报告结构拆解。4.1 实验数据记录跑几次、记什么、怎么呈现操作系统实验的数据分两类确定性数据和随机性数据。调度算法的周转时间在给定输入下是确定的跑一次即可页面置换的缺页率在不同序列下不同需要跑多组。我一般会准备 5 组输入序列每组跑 3 次取平均记录缺页次数和缺页率。表格示例算法序列1序列2序列3序列4序列5平均缺页率FIFO151214131670%LRU121011101356%Clock131112111461%逻辑说明表格里的数字是缺页次数总访问次数固定为 20缺页率 缺页次数 / 20。平均缺页率是 5 组数据的算术平均。报告里要说明序列的生成方式比如随机生成还是手工设计手工设计的序列要给出设计理由。4.2 图表制作折线图、柱状图的选择与标注对比算法性能时柱状图适合展示不同算法的平均指标折线图适合展示指标随参数变化的趋势。比如页面置换实验中横轴是物理块数3、4、5纵轴是缺页率三条折线分别代表 FIFO、LRU、Clock。图表必须标注坐标轴含义、单位、图例。常见错误是只放图不解释或者图里的数据和正文对不上。我一般会在图下面写一段话说明从图中能看出什么趋势比如“随着物理块数增加三种算法的缺页率都下降但 LRU 下降最快”。4.3 结论与误差分析怎么把“跑通了”写成“验证了”结论部分不能只写“实验成功”要写“在什么条件下什么算法表现更好差距有多大”。比如“在物理块数为 3、访问序列长度为 20 的条件下LRU 比 FIFO 少缺页 3 次缺页率降低 15 个百分点”。误差分析针对随机性实验。如果页面序列是随机生成的要说明随机种子、生成范围、样本数量。如果结果波动大要分析原因比如序列长度太短导致统计意义不足。注意报告里不要写“由于时间关系只跑了一组”这类话要么补数据要么说明为什么这组数据有代表性。5. 避坑与排查操作系统实验里最容易翻车的五个点5.1 内核模块加载失败Invalid module format现象insmod mymodule.ko报Invalid module format或disagrees about version of symbol module_layout。原因编译模块的内核版本和当前运行内核版本不一致。常见于虚拟机模板升级后头文件没跟着更新。解决用uname -r确认当前内核用modinfo mymodule.ko查看模块的vermagic字段两者必须一致。不一致时重新安装对应版本的头文件并重新编译。5.2 多线程实验结果不稳定counter 每次跑都不一样现象不加信号量的竞态实验shared_counter每次运行结果不同有时接近 200000有时差很多。原因shared_counter不是原子操作两个线程可能同时读到同一个值。CPU 核心数、调度时机都会影响结果。解决这是预期现象报告里要记录多次运行的结果分布。如果加了信号量后仍然不稳定检查sem_wait和sem_post是否配对或者是否误用了sem_trywait。5.3 页面置换实验缺页率异常高序列生成有问题现象LRU 的缺页率超过 90%明显不合理。原因页面序列的生成范围太大比如物理块数为 3 但页面号范围是 0-99导致几乎每次访问都不在内存中。解决页面号范围应该和物理块数匹配常见做法是页面号在 0 到capacity 2之间随机生成。报告里要说明序列生成规则。5.4 文件系统实验 inode 分配失败位图越界现象alloc_inode返回 -1但inode_count远小于MAX_INODES。原因inode_bitmap数组越界写入了其他数据导致部分位被意外置 1。常见于循环边界写错比如i MAX_INODES。解决检查所有遍历位图的循环边界必须是 MAX_INODES。用valgrind或gdb检查数组越界。5.5 报告数据和代码输出不一致复制粘贴惹的祸现象报告里的缺页次数和程序实际输出对不上。原因改了代码但忘了重新跑数据或者从不同次运行中混用了数据。解决每次改代码后重新跑全部实验用脚本自动记录输出到文件报告数据直接从文件读取。我一般会写一个run_all.sh把所有实验跑一遍并保存日志。6. 进阶技巧用脚本自动化实验数据采集与报告生成操作系统实验的重复性很高手动跑数据、抄结果、画图表非常耗时。我后来养成的习惯是用 shell 脚本批量运行实验用 Python 解析输出并生成图表最后用 Markdown 模板拼出报告初稿。这套流程能把报告撰写时间从两天压缩到半天。6.1 批量运行与日志采集#!/bin/bash # run_all.sh - 批量运行页面置换实验并保存日志 SEQUENCES(seq1.txt seq2.txt seq3.txt seq4.txt seq5.txt) ALGOS(fifo lru clock) for algo in ${ALGOS[]}; do for seq in ${SEQUENCES[]}; do echo Running $algo on $seq ./page_replace --algo $algo --input $seq results.log done done逻辑说明外层循环遍历算法内层循环遍历序列每次运行追加到results.log。参数上--algo指定算法--input指定序列文件。日志格式要统一方便后续解析。6.2 用 Python 解析日志并生成图表import re import matplotlib.pyplot as plt # 解析日志 data {} with open(results.log) as f: for line in f: m re.match(ralgo(\w) seq(\w) faults(\d), line) if m: algo, seq, faults m.group(1), m.group(2), int(m.group(3)) data.setdefault(algo, []).append(faults) # 画柱状图 algos list(data.keys()) avg_faults [sum(data[a])/len(data[a]) for a in algos] plt.bar(algos, avg_faults) plt.xlabel(Algorithm) plt.ylabel(Average Page Faults) plt.title(Page Replacement Comparison) plt.savefig(comparison.png)逻辑说明正则表达式提取算法名、序列名和缺页次数按算法分组求平均用 matplotlib 画柱状图。参数上re.match的模式要和日志格式严格对应否则解析为空。生成的图片直接插入报告。6.3 报告模板与自动填充我一般用 Markdown 写报告把固定部分写成模板变量部分用占位符标记然后用 Python 替换。template ## 实验结果 | 算法 | 平均缺页次数 | |------|--------------| {algo_rows} algo_rows \n.join(f| {a} | {sum(data[a])/len(data[a]):.1f} | for a in algos) report template.format(algo_rowsalgo_rows) with open(report.md, w) as f: f.write(report)逻辑说明algo_rows动态生成表格行format替换占位符。参数上:.1f保留一位小数。这样每次跑完实验报告初稿自动生成只需要补充分析和结论。这套流程的边界在于如果实验框架的输出格式不统一正则解析会失败。我一般会先跑一次确认输出格式再写解析规则。另外图表风格要统一坐标轴标签、图例位置、字体大小最好固定下来避免报告里图表风格混乱。希望帮到你。本文还有配套的精品资源点击获取