资讯动态

NACHOS操作系统课设全流程指南:从编译环境到答辩避坑

发布时间:2026/9/30 10:05:22 来源:尧图企业网站定制
简介这份山东大学2020级操作系统课程设计项目基于NACHOS-3.4教学内核适合操作系统方向本科生与自学教学OS的开发者。项目以C语言为主实现覆盖进程管理、内存管理、文件系统等核心模块目录组织清晰便于对照实验要求进行调试与修改。压缩包共包含633个文件大小约7.6MB既有目标文件、依赖文件、C与C源码、头文件也有makefile和nachos可执行文件并附有PDF、Word说明文档方便梳理实验步骤与运行结果。目前已有170人浏览学习说明其内容和结构具备一定参考价值。通过这份资料可以快速掌握NACHOS环境下的编译链接与调试方法理解教学内核的运行机制同时为线程调度、同步原语和文件系统等实验提供可复用的代码基础与排错经验对完成类似操作系统课设具有直接帮助。1. 用 NACHOS 把操作系统课设跑通别让代码卡在编译这一步每年操作系统课设总有人抱着我代码都写完了的心态走进实验室结果连主程序都没编译过。山东大学2022操作系统课设2020级指定使用 NACHOS-3.4-UALR-2022它不是给你一个现成操作系统而是给你一个运行在 x86 主机上、内部模拟 MIPS CPU 的教学内核骨架。线程、同步、系统调用、虚拟内存、文件系统每个模块都留了洞课设的本质就是把这些洞补上并跑出结果。真正劝退人的不是算法难度而是这代代码和当代编译器之间的兼容性。这篇笔记按从环境到答辩的顺序把每个环节的命令、参数和翻车点写清楚让新手能照做熟手能避坑。2. 搭出 NACHOS-3.4-UALR-2022 的编译环境Ubuntu 22.04 与 Makefile 的相处之道NACHOS 主体是 C构建方式沿用早期 Unix 的 make 体系。你最先要搞定的不是实验本身而是让编译器接受二十年前的代码风格。我见过太多小组卡在环境上最后只能拿别人的可执行文件去演示答辩一问三不知。环境这关必须自己过一遍后面每个实验才有底气改代码。2.1 为什么在 Ubuntu 上跑而不是在 Windows 直跑NACHOS 依赖 POSIX 接口和信号处理Windows 原生不具备Cygwin 和 MinGW 虽然能跑但字节序检测、中断模拟这些底层行为在 Windows 工具链下经常静默出错。最常见做法是 VirtualBox 或 VMware Workstation 装 Ubuntu 虚拟机。VMware Workstation 对 Ubuntu 的支持成熟分配 2 核 CPU、2GB 内存、20GB 虚拟磁盘就足够跑完整套课设。Ubuntu 版本建议选 22.04 LTS它自带 gcc 11和 NACHOS-3.4-UALR-2022 的 Makefile 配合最稳。24.04 的 gcc 13 在部分模块上会报更多警告和错误把时间耗在编译器兼容上不值得。WSL 用户建议用 WSL2WSL1 的 API 转换层在 NACHOS 的多线程模拟下容易出现间歇性崩溃而且每次崩的时机还不一样极难排查。2.2 依赖安装与目录结构拿到源码包后先建一个干净的目录路径不要带中文和空格NACHOS 的 Makefile 依赖关系是相对路径拼出来的路径一长就容易出幺蛾子。sudo apt update sudo apt install -y build-essential g make gdb unzip mkdir -p ~/oslab unzip NACHOS-3.4-UALR-2022.zip -d ~/oslab cd ~/oslab/NACHOS-3.4-UALR-2022 ls -Fbuild-essential 是元包装上之后 gcc、g、make 一起到位。gdb 后面调试线程和用户程序都要用建议一开始就装好。unzip 缺了就多装一个 unzip。apt update 如果卡在软件源把源换成国内镜像即可这一步和课设无关但卡住会让整个小组停摆。解压后你会看到这些目录对照着找代码不迷路目录职责课设对应模块threads/内核启动、Thread 类、同步原语线程与同步实验machine/模拟硬件CPU、内存、TLB、磁盘、时钟不改也要读userprog/用户程序加载、异常与系统调用系统调用实验filesys/文件系统、磁盘块管理、目录文件系统实验vm/虚拟内存扩展内存实验test/用户测试程序编译生成 .noff验收用例bin/coff2noffCOFF 转 NACHOS 可加载格式工具2.3 make 是课设的第一道关卡编译 NACHOS 内核要进 threads 目录这里不是根目录。很多新手在根目录执行 make 发现啥也没有以为源码坏了。cd threads make depend makemake depend 生成头文件依赖关系它的输出是一堆往 Makefile 里写依赖行的日志不是编译过程正常。make 开始后会有大量编译警告比如 old-style cast、deprecated conversion这些不用管只要不报 error 就继续。如果报cc1plus: error: unrecognized command line option -fwritable-strings说明源码包里的 Makefile 还留着 gcc 4.0 时代的编译选项直接打开 threads/Makefile 找到包含 write-strings 的那行删掉保存后重新 make。UALR-2022 版本理论上清理过但你手里的包可能是从同学、网盘转手的保不齐混着旧版文件。链接阶段如果报undefined reference to __gxx_personality_v0这是老代码在 gcc 3 时代留下的坑检查 Makefile 里是不是用 gcc 而不是 g 做链接改成 g 即可。make 成功后在 threads 目录下生成 nachos 可执行文件ls -l nachos ./nachos不带参数运行会打印 usage 参数列表能看到 -x、-rs、-d、-m、-f、-cp、-p 这些开关。这一步过掉环境关就过了七成。接下来验证用户程序工具链NACHOS 的 test 程序要用 MIPS 交叉编译器sudo apt install -y gcc-mips-linux-gnu binutils-mips-linux-gnu cd ../test make ls -l *.nofftest/Makefile 用 mips-linux-gnu-gcc 把 C 程序编译成 MIPS 大端格式的 COFF再交给 coff2noff 转成 NACHOS 认识的 noff 格式生成 add.noff、halt.noff 这些文件。漏掉 coff2noff 这一步是常见翻车点有人直接拿 mips-linux-gnu 编译出的 ELF 文件去喂 nachos结果加载失败或段错误。跑通第一个用户程序cd ../threads ./nachos -x ../test/add.noff-x 是执行用户程序的开关路径要相对于当前目录。add.noff 是 test/add.c 编译出来的它做一堆加法运算最后调用 Halt 系统调用退出模拟器。看到输出并返回 shell说明环境全链路通了。建议顺手装一下 file 命令binutils 里有后面排查时file add.noff能直接告诉你文件是 ELF、COFF 还是 NOFF比猜快得多。3. 从线程到文件系统NACHOS-3.4 的四条实验主线编译过了只是入场券。NACHOS 课设真正要做的事集中在四个模块线程与同步在 threads 目录系统调用在 userprog 目录虚拟内存在 vm 和 machine文件系统在 filesys。每条主线都有对应的启动参数和验证方法先整体走一遍再往深改。3.1 Thread 与信号量先让并发不玄学NACHOS 的 Thread 是内核级线程但不是宿主机 Linux 线程它靠 machine 目录里的 Timer 在时钟中断里做上下文切换。每个 Thread 对象保存栈指针、寄存器现场调用 Fork 之后线程进入就绪队列Yield 主动让出 CPUFinish 结束线程。同步原语 Semaphore 在 synch.h 里本质是一个整数加等待队列P 操作减一小于 0 就睡眠V 操作加一唤醒一个等待者。在单 CPU 模拟器里开关中断就能保证原子性。写一个最小生产者消费者替换 main.cc 里的 ThreadedTest 就能跑#include system.h #include synch.h Semaphore *empty NULL; Semaphore *full NULL; void Producer(int dummy) { for (int i 0; i 5; i) { empty-P(); // 缓冲区有空位才继续 printf([P] produce %d\n, i); full-V(); // 通知消费者 } } void Consumer(int dummy) { for (int i 0; i 5; i) { full-P(); // 有数据才消费 printf([C] consume %d\n, i); empty-V(); // 释放空位 } } void ThreadedTest() { empty new Semaphore(empty, 1); full new Semaphore(full, 0); Thread *t new Thread(producer); t-Fork(Producer, 0); Consumer(0); }Fork 接收两个参数第一个是线程函数指针第二个是传给函数的 intNACHOS 线程函数签名都是void func(int)。Semaphore 构造函数第二个参数是初值empty 初值决定刚开始有多少空位设成 0 的话生产者一上来就卡住。缓冲区上限从 1 改成 N就是有界缓冲生产者不会覆盖未消费数据消费者不会读出空数据。这里要拎清一个操作系统知识点NACHOS 里 Thread 才是调度单位AddrSpace 是进程的地址空间数据。做多线程同步实验时不需要用户进程直接在 ThreadedTest 里 Fork 就行做系统调用实验时才涉及 AddrSpace。这个区分想明白后面就不会把两套机制混着改。3.2 用户进程与系统调用add.noff 是如何被加载的NACHOS 运行用户程序不是宿主机 exec而是打开 noff 文件按 test/bin/noff.h 定义的可执行格式读出代码段和初始化数据段的虚拟地址与大小。./nachos -x ../test/add.noff执行时userprog/addrspace.cc 读取文件头分配物理内存建立页表然后让模拟 CPU 从用户态入口开始执行。用户程序执行 syscall 指令时模拟 CPU 抛出 SyscallExceptionuserprog/exception.cc 里的 ExceptionHandler 按系统调用号分发到具体实现。在 test/add.c 里写一个会打印结果的程序#include syscall.h int main() { int i, sum 0; for (i 1; i 10; i) sum i; Print(sum %d\n, sum); Halt(); }Print 系统调用内部会调用 WriteConsole 把字符串输出到控制台。注意用户程序结束时要显式调用 Halt否则模拟器不会自动退出CPU 会继续取指跑到无效地址后抛 BusError答辩现场看起来就是程序死了。调试输出用-d开关控制./nachos -d u -x ../test/add.noff-d u 表示跟踪用户程序执行会把每条指令和系统调用都打出来输出量大适合在验证某个系统调用时加。-rs 100可以固定随机种子让每次运行的时间片随机序列一致这个参数在后面排查同步死锁时是后悔药级别的工具。3.3 虚拟内存与 TLB改哪里才能真正看到缺页模拟 CPU 每次取指令和数据都要过 machine/translate.cc 的 Translate 函数先从 TLB 查TLB 未命中才查页表。machine/machine.h 里 TLB 默认只有 4 项所以多线程或多进程场景下 TLB 缺失率很高内存实验就是围绕这个展开的。物理内存大小用 -m 参数控制单位是字节。NACHOS 页大小是 128 字节-m 128表示只有 1 个物理页框。想看地址转换全流程加 -d m./nachos -m 128 -d m -x ../test/test.noff-m 128 时程序加载到一半就会因物理页框不够而报错退出这个失败本身就是观察点AddrSpace::Load 分配页框失败会触发断言。想成功做实验-m 至少要能装下整个程序再通过调整 -m 对比 TLB 缺失次数。注意 NACHOS 原始实现里页表全在内存没有磁盘换入换出所以这里说的缺页和 Linux 的 demand paging 不是一回事。课设扩展方向通常是自己补一个从 noff 文件按需加载页面的机制这也正是 vm 目录存在的意义。3.4 文件系统格式化、拷入、读出NACHOS 文件系统跑在 machine/disk.cc 模拟的磁盘上filesys/filesys.cc 管理目录和空余块表。只有格式化之后才有目录结构OpenFile 通过 FileHeader 记录扇区索引。常用命令是一条龙./nachos -f ./nachos -cp ../test/add.noff addFile ./nachos -ls ./nachos -p addFile-f 格式化磁盘相当于 mkfs-cp 把宿主机文件拷入模拟磁盘目标文件名不能带路径-ls 列出目录内容-p 把文件内容打印出来。命令顺序不能乱先 -f 再 -cp因为格式化的瞬间会清掉磁盘上所有内容。重复做实验时我习惯 f 一遍、cp 一遍、跑一遍验证让每个实验都从干净状态开始。模拟磁盘容量很小别把大文件拷进去超出容量报错是预期行为。文件系统实验要求改目录项格式或加二级索引时改之前先用这三个命令把原始行为记录一遍改坏了才有对照基线。4. 课设拿高分的三个实验改法从复制代码到能答辩基础命令跑通后就进入真正的课设阶段。这一章给三个高频实验的改造路径每个都落到具体函数和参数做完之后你手里的 not 只是能跑而是能讲清楚为什么这么改。4.1 生产者消费者实验的三个验收点实验要求是验证同步原语正确工作不是把代码贴上去就完事。老师验收时会盯着三个点看初值是否和缓冲上限匹配、P/V 是否成对、死锁时能不能自己定位。把上文的缓冲上限抽象成常量#define BUFFER_SIZE 5 Semaphore *empty new Semaphore(empty, BUFFER_SIZE); Semaphore *full new Semaphore(full, 0);empty 初值必须等于 BUFFER_SIZEfull 初值必须为 0。如果两者都设成 BUFFER_SIZE消费者会先消费不存在的空位直接下溢。P/V 配对检查最简单的方法是数花括号每个 P 一定对应一个执行路径上的 V不能只在 if 分支里 V。死锁定位用-rs固定种子复现再在每个 P/V 前后加 printf看卡在哪个信号量上。还要会改配置把 BUFFER_SIZE 改成 1就是互斥锁改成 10就是有界缓冲。答辩时老师大概率会问你这个信号量初值为什么这么设答案就是empty 表示空位数full 表示数据数两者之和恒等于 BUFFER_SIZE。4.2 从 Halt 到 Exec系统调用实现的四个步骤实验要求新增或改造系统调用时套路是固定的四步。以 Exec 为例用户程序里调用后操作系统的目标是加载一个可执行文件并返回给它一个新的进程 id。第一步在 userprog/syscall.h 里确认系统调用号存在。第二步在 test/ 写一个调用它的 C 程序重新编译生成 .noff。第三步在 exception.cc 的 ExceptionHandler 里加 casecase SC_Exec: { // 系统调用参数在寄存器 r4是用户空间的字符串地址 int virtAddr machine-ReadRegister(4); char *name new char[64]; ReadStringFromUser(virtAddr, name, 64); // 从模拟内存拷贝到宿主内存 int pid pTab-Exec(name); // 创建新地址空间并加载程序 machine-WriteRegister(2, pid); // 返回值写入 r2 delete[] name; break; }ReadStringFromUser 是把用户空间字符串读到内核内存的工具函数常见实现是逐字节读模拟内存。pTab 是进程表对象Exec 方法内部会新建 OpenFile、AddrSpace加载 noff返回 pid。这四步缺一不可尤其是 WriteRegister(2, ...)不写返回值用户程序拿到的就是一个随机数。调试这个实验先跑一个调用 Exec 的测试程序用-d u看系统调用是否进入对应 case。如果一进 Exec 就段错误先把 name 指向的字符串打印出来多半是用户虚拟地址翻译失败。4.3 TLB 缺页统计与 LRU 替换给答辩准备一张数据表内存实验最常做的是统计 TLB 缺失率然后把随机替换改成 LRU。统计点放在 translate.cc 的 Translate 函数里TLB 命中走一条路未命中查页表走另一条路static int tlbHit 0; static int tlbMiss 0; if (tlbValid) { tlbHit; } else { tlbMiss; // 原有查页表逻辑 }改动点极小但能产出一个让答辩老师眼睛一亮的数字不同 -m 参数下 TLB 缺失率的变化。LRU 改造需要给页表项加一个时间戳字段每次 TLB 或页表命中时更新替换时挑时间戳最小的那项踢出去。原始 NACHOS 的替换策略是 RandomReplace你只需在 machine/machine.h 里把 TLB 项增加 lastUse 字段translate.cc 里替换处改成找最小值。验证方式是用同一组测试程序分别跑 FIFO、Random、LRU 三个版本记录缺页次数做成一张三行对比表。这张表能直接贴在实验报告里答辩时老师问LRU 比原来好在哪里你把数字摆出来比背定义有用得多。注意改 machine 目录下的核心结构会影响所有模块。改之前先备份改完 TLB 字段后把线程和文件系统实验回归一遍防止把 TLB 改崩了导致前面所有实验跟着翻车。5. NACHOS 避坑手册编译、调试与答辩现场的 5 个翻车点NACHOS 课设的坑集中分布在编译器兼容、文件格式、死锁复现、答辩环境这几个环节。下面五条是我见过的最高频翻车点每一条都按现象、原因、解决写遇到直接照做。5.1 make 报-fwritable-strings无法识别现象make 刚执行没多久就报cc1plus: error: unrecognized command line option -fwritable-strings编译直接中断。原因这个编译选项在 gcc 4.0 之后被移除老版本 NACHOS 的 Makefile 里残留着它。UALR-2022 版本的官方包里一般清理过但网盘流传的副本经常混着旧文件。解决编辑 threads/Makefile搜 write-strings把包含它的整行删除重新 make。如果还报了其他类似-Wno-write-strings的选项同样删掉。这种选项删掉不影响代码正确性只是去掉一个历史遗留的字符串常量优化开关。5.2 运行 -x 用户程序秒退且无任何输出现象./nachos -x ../test/add.noff执行后一瞬间就返回 shell没有打印任何内容或者直接报 Segmentation fault。原因绝大多数情况是 add.noff 根本不存在或者你拿的是 ELF/COFF 原文件没有经过 coff2noff 转换。test 目录只运行了 mips-linux-gnu-gcc却漏了生成 noff 的那一步。解决先ls -l ../test/*.noff确认文件存在。再用file ../test/add.noff看文件类型正常应该显示 NOFF 相关描述如果显示 ELF 或 COFF回 test 目录执行 make把 coff2noff 那一步跑完。NACHOS 只认 noff 格式ELF/MIPS 的直接喂进去就是段错误。5.3 程序卡死不动只有 CtrlC 能结束现象运行生产者消费者或系统调用实验程序在某个输出处停住CPU 占用率高没有任何报错等你 CtrlC。原因这是死锁。信号量初值设置错误、P/V 顺序写反、等待条件永不满足都会让线程全部睡眠而模拟器没有超时自杀机制只能一直跑时钟中断。解决先用-rs 100固定随机种子让每次运行的时间片序列完全一致这样死锁是稳定复现而不是偶发。接着在每个信号的 P/V 前后加 printf打印信号量名和当前值运行一次看线程最后停在哪一行。死锁通常能归结到某个 V 没执行或者某个 P 的初值导致永久阻塞。修完记得把 printf 删掉或注释掉避免答辩时输出刷屏。5.4 gdb 断点打不进去变量全是optimized out现象用 gdb 调试 nachos在 ExceptionHandler 或 Translate 里下断点程序跑过去但没停或者 print 变量显示optimized out。原因Makefile 默认开了优化选项gcc 在 -O1/-O2 下会重排指令、消除局部变量断点地址对应不上去。解决编辑 threads/Makefile把 CXXFLAGS 里的优化选项统一改成-O0 -g3然后make clean make重新编译。注意 clean 会删掉 nachos 可执行文件但不会影响 test 目录里已经生成的 .noff。重新编译后重新下断点变量能读了单步也跟得上源码了。代价是运行速度慢但课设场景无所谓。5.5 答辩现场换了一台电脑原本正常的程序编译失败现象宿舍里能跑到了机房换了台机器重新编译报错一堆包括找不到 mips-linux-gnu-gcc、头文件路径不对、Makefile 里绝对路径失效。原因你把编译好的 nachos 拷过去了但没带交叉编译器、没带完整源码树或者 Makefile 里用了写死的绝对路径换机器后自然失效。解决答辩前在演示机器上完整跑一遍解压、装依赖、make、跑 add.noff四步。依赖安装命令打印出来贴在报告附录里机房机器版本未知时优先装 Ubuntu 22.04 镜像的虚拟机作为演示环境和开发环境一致最稳妥。源码包整个带上 U 盘不要只带编译产物因为老师大概率会让你现场改一个小参数重编译没有源码就露馅了。6. 把 NACHOS 课设从能跑改成能讲清楚一份可复现的实验记录课设答辩看的不是代码多惊艳而是你能不能在一分钟内讲清改了什么、为什么改、怎么验证。我自己的习惯是每完成一个实验就写一段记录包含改动文件、关键函数、启动命令、预期输出四行。比如系统调用实验就写改动 userprog/exception.cc 新增 SC_Print 分支启动命令是 ./nachos -x ../test/print.noff预期打印一行字符串。这份记录不追求长但每个实验都能按它重新跑一遍比临时记在脑子里强太多。代码里也别只写功能注释写接口说明。NACHOS 的系统调用实现都有一个固定模式从寄存器读参数、执行逻辑、把结果写回寄存器。在 ExceptionHandler 里加一行注释r4 是用户地址r2 是返回值三个月后回来读代码不用重新翻函数签名。默认 Makefile 开了优化我吃过大亏后来一上来就把 CXXFLAGS 改成 -O0 -g3顺手在代码里保留所有调试 printf 的开关用条件编译包起来答辩现场演示失败时打开开关重跑一次立刻定位到是数据没写进去还是页表没刷新。用 git 的后悔药也很关键。不用推远程仓库本地 git init 就够每次实验改完提交一次。改 TLB 结构改崩了文件系统实验git checkout 回滚改系统调用改挂了线程实验git stash 对比差异。NACHOS 模块互相依赖没有版本控制你可能一个下午都在从一个坑跳到另一个坑。最后讲个教训答辩前夜不要大规模重构。把能跑的版本先备份再动手加“最后一点优化”。我见过有同学在演示前两小时把 LRU 替换改成时钟算法结果新算法有一个索引越界没查出来整场演示变成调试直播。课设评价的是你已经做到的东西不是想象中完美的东西。先把每个实验稳定跑通再谈改进顺序不能反。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑