
简介这份资源是北京交通大学操作系统课程的实验答案与报告合集面向正在修读操作系统实验课、需要参考实现思路与报告写法的本科生。内容覆盖进程管理、内存管理、文件系统、死锁与资源分配、页面置换算法及权限管理等核心实验模块每个实验均配有可运行的源码与对应的实验报告文档便于对照理解调度算法、地址转换、缺页中断、LRU替换策略、银行家算法等关键知识点。压缩包共41个文件以26个C语言源文件为主另有5个C文件、4个Markdown报告、2个头文件及汇编、文本等辅助文件整体约73KB结构按lab_1至lab_5分目录组织便于按实验模块检索。目前已有201人学习下载。读者可从中获取完整的实验代码实现、报告撰写框架、结果分析思路与常见问题处理方式适合作为动手实践时的参考与查漏补缺之用。1. 操作系统实验报告到底该交什么从“答案.zip”说开去很多人第一次看到“北京交通大学操作系统实验答案和报告.zip”这类资源第一反应是找一份能直接交差的文档。但真正做过这门课的人都知道操作系统实验的评分点从来不在“答案”本身而在你有没有把进程调度、内存管理、文件系统这些机制在自己的代码里跑通。北京交通大学操作系统实验通常围绕 Nachos 或 xv6 这类教学内核展开实验报告需要呈现的是你对系统调用、线程调度、虚拟内存替换算法的理解与实现过程。如果你只是把别人的报告改个名字交上去答辩时老师问一句“你的 LRU 是怎么处理时钟指针回绕的”就会当场翻车。这篇笔记面向正在做操作系统实验的本科生和自学者把实验报告里真正该写的东西、代码该怎么落地、参数怎么调、哪些坑一定会踩按可复现的顺序讲清楚。操作系统实验不是背概念是让内核按你的意图跑起来。2. 先搞清楚实验环境Nachos、xv6 还是自研框架2.1 三类主流教学内核的选型差异操作系统实验的“答案”之所以不能通用是因为不同学校、不同年份用的教学内核不一样。北京交通大学操作系统课程常见的是 NachosJava 或 C 版本和 xv6RISC-V 或 x86 版本也有部分年份使用自研的简化内核框架。Nachos 的优势是代码量小、模块清晰线程调度和内存管理都有现成的骨架你只需要填核心算法缺点是构建系统老旧在较新的 GCC 或 JDK 上编译经常报错。xv6 更接近真实 Unix适合做系统调用和页表实验但调试门槛高一个指针错误就能让整个内核静默崩溃。选型判断方法很简单看课程群或实验指导书里提到的目录名。出现threads/、machine/、userprog/这类目录基本是 Nachos出现kernel/、user/、Makefile里带qemu的基本是 xv6。确定内核之后再去搜对应的实验答案才有意义否则你拿到的“答案”可能连编译都过不了。2.2 环境搭建的最小可复现步骤以 xv6 RISC-V 版本为例在 Ubuntu 22.04 上搭建实验环境的命令如下。这套步骤在 WSL2 和原生 Linux 上都验证过macOS 需要额外处理交叉编译工具链。# 安装 RISC-V 交叉编译工具链和 QEMU sudo apt update sudo apt install -y git build-essential gdb-multiarch qemu-system-misc \ gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu # 克隆 xv6 源码这里用课程常见分支实际以老师给的仓库为准 git clone https://github.com/mit-pdos/xv6-riscv.git cd xv6-riscv # 编译并启动 make qemu编译成功后你会看到xv6 kernel is booting的输出然后进入 shell。如果卡在make阶段报riscv64-unknown-elf-gcc: not found说明工具链没装全检查gcc-riscv64-linux-gnu是否安装。参数上make qemu默认使用单核做调度实验时需要改成make qemu CPUS3才能观察多核行为。Nachos 的构建通常是make depend然后make在proj1到proj4目录下分别执行Java 版本需要 JDK 8 或 11JDK 17 以上会因为模块系统限制报错。提示不要用最新版工具链硬扛教学内核的 Makefile 往往写死了旧路径。遇到编译错误先看 Makefile 里的CC和CFLAGS再决定是改代码还是降版本。3. 进程与线程实验调度算法怎么写进报告里3.1 调度器的代码骨架与关键数据结构进程调度是操作系统实验里最常考的部分Nachos 的scheduler.cc和 xv6 的proc.c是核心文件。以 xv6 为例默认的轮转调度在kernel/proc.c的scheduler()函数里它遍历proc数组找RUNNABLE的进程。如果你要做优先级调度需要改三处进程控制块加priority字段、allocproc初始化优先级、scheduler按优先级选进程。// kernel/proc.h 中给 proc 结构体加字段 struct proc { // ... 原有字段 int priority; // 优先级数值越小优先级越高 int runtime; // 已运行时间片用于统计 }; // kernel/proc.c 中修改 scheduler void scheduler(void) { struct proc *p; for(;;) { intr_on(); int highest -1; struct proc *selected 0; for(p proc; p proc[NPROC]; p) { acquire(p-lock); if(p-state RUNNABLE) { if(highest -1 || p-priority highest) { highest p-priority; selected p; } } release(p-lock); } if(selected) { acquire(selected-lock); selected-state RUNNING; release(selected-lock); swtch(c-context, selected-context); } } }这段代码的逻辑是每次调度时扫描所有 RUNNABLE 进程选优先级数值最小的运行。acquire和release保护进程状态避免多核竞争。参数上priority的取值范围建议设为 0 到 200 最高20 最低这样在报告里画优先级分布图比较直观。runtime字段用于统计每个进程实际占用 CPU 的 tick 数在时钟中断里累加。3.2 实验报告里必须出现的三张表老师看报告时不会逐行读代码但一定会看数据。调度实验至少要有三张表进程到达时间与优先级表、调度顺序甘特图对应的数据表、平均等待时间和周转时间对比表。第一张表列出每个进程的 PID、到达时间、优先级、需要运行时间第二张表按时间片记录当前运行的进程第三张表算 FCFS、RR、优先级调度三种算法的平均等待时间。进程到达时间优先级需要时间FCFS 等待RR 等待优先级等待P1035025P2113540P3222863这张表的数据要来自你实际跑出来的日志不能手编。xv6 里可以在scheduler里加printf打印切换记录Nachos 用DEBUG(s)打开调度调试开关。报告里写清楚“数据来源在scheduler函数第 X 行插入打印语句运行make qemu后从串口输出采集”这样才有说服力。3.3 优先级反转与饥饿问题的处理优先级调度最容易被问到的坑是优先级反转和低优先级进程饥饿。如果你在报告里只写了“按优先级选进程”老师大概率会追问“低优先级进程永远得不到 CPU 怎么办”。常见做法是引入老化机制每次调度时给所有等待进程的优先级减一最低降到 0。代码上就是在scheduler扫描前加一个循环遍历 RUNNABLE 进程并调整priority。// 老化防止低优先级进程饥饿 for(p proc; p proc[NPROC]; p) { acquire(p-lock); if(p-state RUNNABLE p-priority 0) { p-priority--; } release(p-lock); }这段代码放在调度循环的开头每次调度前执行一次。参数上老化步长设为 1 比较温和如果实验要求快速收敛可以设为 2。注意老化不能把优先级降到负数所以判断条件里要有p-priority 0。报告里要解释清楚老化增加了低优先级进程被选中的概率但不会完全消除优先级的影响适合交互式系统和批处理系统混合的场景。4. 内存管理实验页面置换算法的代码与数据4.1 LRU 和 Clock 算法的实现差异内存管理实验通常要求实现 FIFO、LRU、Clock 三种页面置换算法并对比缺页率。Nachos 的machine/translate.cc和 xv6 的kernel/vm.c是主要修改点。FIFO 最简单用一个队列记录页面进入顺序LRU 需要记录每个页面的最近访问时间每次访问更新Clock 是 LRU 的近似用环形链表和访问位。// Clock 算法核心逻辑伪代码风格适配 Nachos int clock_hand 0; int use_bit[NUM_PAGES] {0}; int pick_victim() { while(1) { if(use_bit[clock_hand] 0) { int victim clock_hand; clock_hand (clock_hand 1) % NUM_PAGES; return victim; } else { use_bit[clock_hand] 0; // 给第二次机会 clock_hand (clock_hand 1) % NUM_PAGES; } } }Clock 的关键是“第二次机会”访问位为 1 时清零并跳过为 0 时选中淘汰。参数上NUM_PAGES是物理帧数量通常设为 4 到 8 做实验比较合适太小缺页率爆高太大看不出算法差异。clock_hand是全局指针每次淘汰后前移一位。报告里要给出三种算法在不同帧数下的缺页率曲线帧数取 2、4、6、8访问序列用课程给的 trace 文件或自己生成的随机序列。4.2 缺页率数据的采集与报告呈现缺页率不能靠估算要在page fault handler里加计数器。Nachos 在ExceptionHandler的PageFaultException分支里累加xv6 在usertrap的scause 13或15分支里累加。采集到的数据按“算法 帧数”分组报告里用表格呈现。帧数FIFO 缺页率LRU 缺页率Clock 缺页率262.5%58.3%59.7%441.2%35.8%37.1%628.9%24.4%25.6%819.3%16.7%17.2%数据解读要写清楚LRU 在帧数较少时优势明显因为局部性原理发挥作用Clock 作为近似算法缺页率略高于 LRU 但实现开销小FIFO 在帧数增加时改善最慢因为可能出现 Belady 异常。这些结论要结合你的实际数据说不能照抄教科书。4.3 页面置换实验的三个必调参数第一个参数是物理帧数量决定实验的对比维度第二个参数是访问序列长度太短统计意义不足建议至少 1000 次访问第三个参数是随机序列的局部性强度用 80/20 原则生成即 80% 的访问集中在 20% 的页面上这样更接近真实程序行为。生成序列的脚本可以放在报告附录里。import random def gen_trace(n, page_range, locality0.8): hot_pages list(range(int(page_range * 0.2))) cold_pages list(range(int(page_range * 0.2), page_range)) trace [] for _ in range(n): if random.random() locality: trace.append(random.choice(hot_pages)) else: trace.append(random.choice(cold_pages)) return trace # 生成 1000 次访问页面范围 0-49 trace gen_trace(1000, 50) print(trace[:20])这段脚本生成带局部性的访问序列locality0.8表示 80% 的访问落在前 20% 的热页面上。参数page_range设为 50 意味着虚拟页面有 50 个物理帧数取 2 到 8 时缺页率差异会很明显。报告里要说明序列生成规则并附上完整脚本这样别人才能复现你的数据。5. 文件系统实验从系统调用到磁盘布局5.1 系统调用链路的完整追踪文件系统实验一般要求实现或扩展系统调用比如open、read、write、close或者加一个getfilenum统计打开文件数。xv6 的系统调用链路是用户态调用syscall()→usertrap()识别scause→syscall()分发 →sys_xxx()执行。Nachos 的链路是UserProcess::ExecuteSyscall()分发到SysOpen、SysRead等函数。以 xv6 加一个sys_getfilenum为例需要改五个地方user/user.h加声明、user/usys.pl加桩、kernel/syscall.h加编号、kernel/syscall.c加分发、kernel/sysproc.c加实现。漏掉任何一处都会报unknown syscall或链接错误。// kernel/sysproc.c 中实现 uint64 sys_getfilenum(void) { struct proc *p myproc(); int count 0; for(int i 0; i NOFILE; i) { if(p-ofile[i]) count; } return count; }这段代码遍历当前进程的打开文件表ofile统计非空项。NOFILE是每个进程最大打开文件数xv6 默认是 16。返回值通过return传给用户态用户态用int n getfilenum();接收。报告里要画出系统调用的完整调用链标注每个文件的行号这样老师能快速定位你的修改点。5.2 磁盘布局与 inode 分配的验证方法文件系统实验的难点在于理解磁盘布局。xv6 的磁盘布局是boot block、super block、log、inode blocks、bitmap、data blocks。做实验时经常需要验证 inode 分配是否正确方法是在ialloc和iupdate里加打印观察 inode 编号和磁盘块号的对应关系。区域起始块块数作用boot01引导扇区super11文件系统元信息log230日志区inode3213inode 数组bitmap451数据块位图data46954数据块这张表要背下来答辩时老师可能让你现场算某个 inode 在哪个块。验证方法是创建一个文件用ffsb或自己写的工具读磁盘镜像看 inode 的addrs数组是否指向预期的数据块。Nachos 的磁盘布局在filesys/目录下Disk类模拟扇区读写FileSystem类管理 inode 和目录。5.3 文件系统实验的调试手段文件系统崩溃是常态调试手段比代码本身更重要。xv6 可以用gdb-multiarch连 QEMU 调试命令是gdb-multiarch kernel/kernel然后target remote :26000。Nachos 用DEBUG(f)打开文件系统调试或者直接在FileSystem::Read里加printf。常见崩溃原因是 inode 锁没释放、位图越界、日志提交顺序错误。报告里要写清楚你遇到的一次崩溃、排查过程和最终修复这比堆砌成功数据更有价值。注意改文件系统代码前先git commit一次崩溃后能快速回滚。血泪经验是不要在没备份的情况下直接改ialloc一个位图越界就能让整个镜像报废。6. 避坑与排查操作系统实验里最容易翻车的五件事6.1 编译通过但运行静默崩溃现象make成功make qemu后内核打印几行就卡死没有任何报错。原因通常是页表映射错误或栈溢出RISC-V 的satp寄存器配置不对会导致取指失败。解决方法是先用make qemu-gdb启动在gdb里layout asm看 PC 停在哪如果是0x0或非法地址检查kvmmake里的映射范围。Nachos 的静默崩溃多半是ASSERT被关掉了在Makefile里加-DDEBUG重新编译。6.2 系统调用返回值为 -1 但 errno 没设置现象用户态调用open返回 -1但errno是 0无法判断失败原因。原因是内核态没有正确设置返回值xv6 的sys_open在失败时应该return -1用户态的errno由usys.pl生成的桩函数处理。检查kernel/syscall.c里的syscall()函数确认返回值赋给了p-trapframe-a0。Nachos 的SysOpen失败时返回 -1但errno要在UserProcess::ExecuteSyscall里设置。6.3 调度实验数据与理论不符现象优先级调度跑出来的平均等待时间比 FCFS 还差。原因通常是优先级设置反了数值大的反而被优先选中。检查scheduler里的比较条件p-priority highest表示数值小优先级高如果你想要数值大优先级高就改成。另一个原因是老化机制太激进低优先级进程被反复提升导致优先级区分度消失。把老化步长从 2 改回 1或者限制老化只对等待超过阈值时间的进程生效。6.4 页面置换实验缺页率异常高现象LRU 的缺页率超过 90%明显不合理。原因可能是访问序列的局部性太弱或者物理帧数设得太小。检查gen_trace的locality参数如果设成 0.5 以下热页面和冷页面没有区分度LRU 退化成随机替换。另一个原因是use_bit数组没有在页面换入时正确初始化导致 Clock 算法一直跳过所有页面。在page fault handler里加打印确认每次换入换出的页面号。6.5 文件系统实验数据丢失现象创建的文件在重启后消失或者ls看不到。原因是日志没有正确提交xv6 的commit函数负责把日志写入磁盘如果log_write的块号记录错误提交时会漏掉数据块。检查log_write里的b-blockno是否在log.start和log.start LOGSIZE之间。Nachos 的持久化依赖synchDisk如果WriteBack没调用数据只留在内存。报告里要写清楚你的持久化验证步骤创建文件、写入内容、退出 QEMU、重新启动、读取文件。7. 从能跑到能讲清楚实验答辩的进阶准备实验报告交上去只是第一步答辩才是真正拉开差距的地方。我自己的习惯是代码跑通后把每个实验的核心函数画成一张调用关系图标注输入输出和关键参数。比如调度实验从scheduler到swtch到usertrapret每个箭头旁边写清楚寄存器变化。这样老师问“上下文切换时哪些寄存器被保存”你能直接指图回答。另一个技巧是准备一个“失败案例集”。我在做 xv6 文件系统实验时曾经因为bmap函数里少判断了一个间接块边界导致大文件写入时覆盖了 inode 区。这个 bug 花了两天才定位但答辩时讲出来老师直接给了加分。失败案例比成功数据更能证明你真的动手了。验证方法上我一般会写一个自动化脚本把实验的输入、编译、运行、数据采集串起来。比如调度实验的脚本make clean make qemu CPUS3 input.txt output.txt然后用 Python 解析output.txt生成缺页率或等待时间表格。这样每次改代码都能快速回归不会因为手动操作漏掉步骤。#!/bin/bash # run_sched_exp.sh - 自动化跑调度实验并采集数据 make clean make qemu CPUS3 sched_input.txt sched_output.txt 21 python3 parse_sched.py sched_output.txt sched_report.csv echo 实验完成数据已写入 sched_report.csv这个脚本把编译、运行、解析三步串起来sched_input.txt里放进程创建命令parse_sched.py负责提取切换记录并计算等待时间。参数上CPUS3模拟多核21把 stderr 合并到 stdout 方便采集。报告里附上这个脚本老师能看出你有工程化思维。最后说一个我踩过的坑不要等到答辩前一天才写报告。操作系统实验的数据和代码是绑定的隔一周再回头看你自己都记不清哪个参数对应哪组数据。我的习惯是每跑完一组实验就立刻把数据填进表格代码注释里写清楚“这组数据对应报告第 X 节”。这样最后组装报告时你只需要串逻辑不用重新跑实验。希望帮到你。本文还有配套的精品资源点击获取