
简介南京大学计算机系统课程ICS2017编程作业项目是一套覆盖模拟器、抽象层、操作系统到应用层的完整教学实验框架面向计算机专业学生或自学系统底层知识的学习者。框架包含NEMU微处理器模拟器、NexusAM硬件抽象层、NanosLite轻量级操作系统及NavyApps示例应用可支撑计算机组成原理、操作系统与程序开发等环节的动手实践。资源为zip压缩包共877个文件以c/h源文件为主配合makefile构建脚本、md说明文档、s汇编代码及少量python辅助脚本整体约18.6MB其中c/h文件对应硬件模拟与系统实现makefile负责工程构建md文档便于按模块阅读实验说明目录结构清晰。目前已有95人学习使用。借助附带的说明文档和PA-main主入口学习者可逐步完成从指令模拟、系统启动到应用运行的全链路实验通过NEMU观察CPU状态在NanosLite中理解进程与内存管理再借助NavyApps实践上层应用开发对建立系统观和排查底层问题很有帮助。 如果你在学校里认真刷过《深入理解计算机系统》大概会有一种错觉书上的字节、指令、栈帧都理解了但一到让程序真正从二进制跑到输出的层面还是会卡住。南京大学计算机系统课程ICS2017的编程作业PA就是来解决这个问题的。它不是一个普通的大作业而是一整套完整的计算机系统教学实验框架里面包含NEMU模拟器、NexusAM抽象层、NanosLite操作系统和NavyApps应用程序目标是让你亲手从零“造”出一台能跑起仙剑奇侠传的电脑。这个项目既能把组成原理、汇编、操作系统、编译原理串成一条线也能当独立项目来学习。这篇文章不打算复述官方文档而是把我对四层框架的理解、每个阶段的推进节奏和实操时踩过的坑整理出来给正在做PA的同学和想补系统底层知识的开发者一点参照。1. 项目概述一门课怎么让你从零“造”出一台电脑1.1 这个项目到底做了什么南京大学ICS课程的核心是PAICS2017版本用的是x86指令集后续课程版本也有切换到RISC-V的。整个项目的组织方式非常像搭积木最底层是NEMU一个用C语言写的教学模拟器负责模拟CPU、内存和基本设备往上一层是NexusAM一个抽象机器层把不同ISA的差异封装成统一API再往上是NanosLite一个极简但五脏俱全的操作系统最顶层是NavyApps一堆跑在NanosLite上的应用程序其中最有名的就是《仙剑奇侠传》游戏。这四个层次加起来不是各自独立练手的demo而是一条完整链路NEMU启动后加载NexusAMNexusAM为NanosLite提供运行环境NanosLite再加载并调度NavyApps里的应用。这件事解决的痛点很明确学计算机组成原理时我们通常在纸上算指令周期学操作系统时又直接面对Linux这种庞然大物。两门课之间的断层导致很多人直到毕业也没能想清楚“一条指令到底是怎么变成屏幕上那幅画面的”。PA用一套可调试、可扩展的迷你实现把这条链路从头到尾打通了。1.2 什么人适合照着这个框架学如果你是正在修ICS或同类课程的学生PA就是主线任务建议按部就班做。如果你已经工作想回炉补系统底层知识也可以拿这个项目当“私人训练场”因为它的代码量在课程框架里算是克制的每个阶段只要求你实现必要的功能理解成本比直接去啃QEMU或者x86手册低得多。需要的基础是C语言能写链表、结构体和指针Linux命令行会用git会基本的add、commit、branch。不会也没关系PA0就是带你把这些工具配好。有不少人会在网上搜“计算机系统概论答案”之类的东西我的看法是答案对分数可能有用但对能力提升几乎为零。这个项目最值钱的部分是调试过程里那些“灵光一现”的时刻比如你发现一条指令译码错误导致整个系统乱跳花两小时定位后那种对指令语义的敏感度是看十遍答案都换不来的。2. 分层设计为什么是NEMU而不是直接上QEMU2.1 NEMU模拟器的核心功能NEMU的全称是NJU EMUlator它的定位很明确一个为了教学而生的模拟器。它不需要模拟现代CPU的乱序执行、cache分级也不需要模拟复杂的外设总线。它只需要做到三件事取指、译码、执行外加少量设备交互。这并不代表QEMU没有价值而是教学场景更看重可控性和可调试性。QEMU体量太大物理内存、设备模型、动态翻译全都堆在一起初学者很难在里面找到一条清晰的“指令执行路径”。在ICS2017的x86版本里你首先要实现的是最基础的指令执行循环从eip指向的内存位置读出一个指令字节解析操作码和ModR/M字段决定操作数宽度和寻址方式然后执行。等你把mov、add、sub、cmp、jcc、call、ret这一类基础指令跑通就可以通过简易调试器来单步观察程序状态。这个阶段很多人会低估难度觉得“指令不就那几条吗”但实际动手时光是一个立即数符号扩展就够你喝一壶unsigned char和signed char混用结果算出来的地址完全不对。NEMU的另一个设计特点是设备方面的“够用就好”。它通过I/O端口映射来访问串口、时钟、键盘和VGA显存。比如NanosLite要向屏幕输出字符实际就是往某个端口写数据NEMU拿到这个端口写操作后再更新图形窗口或者标准输出。这种设计虽然简单却把“CPU和外部设备如何通信”这个核心概念展示得很干净没有总线仲裁也没有DMA就是in/out指令加上中断。2.2 NexusAM抽象层解决了什么问题第一次看到NexusAM这个名字很多人会疑惑明明是要做操作系统为什么中间非要夹一层抽象直接让NanosLite跑在NEMU上不行吗答案是不行至少不优雅。因为NanosLite需要访问时钟、键盘、显存需要处理中断而这些东西在不同ISA上的实现方式完全不一样。如果OS代码里到处是平台相关的寄存器操作那整个项目就退化成“为某台特定模拟器写驱动”失去了通用性。NexusAM的核心思路是定义一个“抽象机器”接口把硬件能力分成几类TRM图灵机提供计算和内存、IOE输入输出提供timer/keyboard/video、CTE中断和异常、VME虚拟内存、MPE多处理器。NEMU只需要针对具体ISA实现这套接口NanosLite的代码完全不关心底层是x86还是RISC-V。这就像家里换了一个牌子的路由器墙上的网口标准不变所有设备照样能上网。ICS2017版本里AM更像是一个“运行时”它把寄存器上下文切换、中断入口这些脏活累活都包住了让你在写OS时能集中精力考虑逻辑。2.3 NanosLite和NavyApps如何组成完整系统NanosLite是一个极简但完整的操作系统内核。它做的事情包括加载ELF格式的可执行文件、维护进程列表、处理系统调用、管理虚拟内存以及通过AM的IOE接口操作设备。应用层发起read/write时实际通过系统调用陷入内核NanosLite再调用AM接口跟NEMU硬件交互。这条链路长度并不长但每一环都真实存在用户态程序 - 系统调用 - 内核 - AM - 设备模拟。NavyApps则是应用集合里面除了hello、计算器这类小工具还有仙剑奇侠传这样带图形界面的游戏。为什么课程要放一个游戏上来因为它几乎覆盖了所有设备键盘输入、时钟计时、显存绘制、甚至音频。如果你能让仙剑跑起来说明整条链路里的中断、系统调用、显示驱动都工作正常。我第一次看到NEMU窗口里出现仙剑标题画面时确实挺激动的那种“从一行C代码到一台能玩游戏电脑”的成就感很难用分数衡量。3. 从PA0到PA4主线任务怎么一步步推进3.1 PA0和PA1环境、模拟器内核与调试器PA0主要是准备工作。你要在Linux环境里装好gcc、make、git、gdb并学会用makefile构建项目。这个阶段可能看起来没什么技术含量但很多人后面卡住就是因为git用不溜。PA的官方文档会要求你在关键节点提交代码因为后面有些实验需要基于前面成果继续开发如果没有版本管理意识改崩了只能原地重来。进入PA1后你要完成NEMU的核心执行循环同时实现一个简易调试器。调试器功能包括si单步执行、info r查看寄存器、x查看内存、p表达式求值、watchpoint监视点。表达式求值这个任务特别适合练递归下降解析你要处理优先级、括号、变量、十六进制数还要支持解引用。很多同学在这里第一次体会到“写一个能解析自己调试命令的程序”和“用scanf硬拆字符串”的区别。监视点则是硬件调试器概念的简化版CPU每执行完一条指令模拟器就检查监视点条件是否满足满足就暂停。这个机制以后在做OS调试时非常有用。3.2 PA2和PA3中断、AM运行时与操作系统PA2的任务是把指令集补全到能处理中断和异常然后实现AM的CTE部分。你需要实现int指令触发软中断、CPU响应中断后保存上下文、跳转到中断入口再通过iret恢复。这里是第一道大坎上下文切换到底要保存哪些寄存器顺序是什么栈指针怎么调整稍不留神就会让后面的OS跑不起来。我在做的时候吃过一次亏少保存了一个标志寄存器结果中断返回后条件跳转全部错乱排查了很久。PA3就是NanosLite了。你要实现装载ELF、系统调用、虚拟内存可能还有分页机制。分页这块特别容易出bug你需要在启动分页前准备好页表并且把内核所在的虚拟地址和物理地址映射好。很多人一开启cr0的PG位就立刻triple fault原因往往是线性地址低端没有映射或者页目录项权限位设置不对。我的建议是先在纸上画出虚拟地址到物理地址的映射关系再动手填页表不要凭感觉写。跑起来之后再用测试程序验证进程切换基本就稳了。3.3 PA4把NavyApps跑起来PA4的内容相对轻松但也有意思把你实现好的NanosLite当底座把NavyApps里的应用程序编译进去让它们在模拟器上真正运行。这里涉及交叉编译和文件系统比如你要用交叉工具链编译出能在NanosLite上跑的ELF还要把多个应用程序打包成一个简单的文件系统镜像让OS能按文件名加载。仙剑奇侠传是PA4的“毕业项目”。它用到键盘、时钟、VGA甚至音频任何一个设备接口出错游戏画面都会异常。比如按键没有反应多半是NEMU的键盘设备中断没有正确触发画面花屏则可能是VGA的绘制地址或像素格式算错了。当你把这些问题一个一个修掉看到游戏开场动画正常播放整个PA才算真正完整收官。4. 实操中真正会踩的坑与排查技巧4.1 指令执行阶段的“翻车点”PA1和PA2最常见的bug集中在译码上。指令长度是可变的比如mov指令可能是mov r/m8, r8也可能是mov r/m32, r32操作码后面跟的ModR/M字节决定了使用寄存器还是内存寻址还决定了后面有没有disp或imm。很多人在解析一个带SIB字节的寻址时直接漏掉SIB导致访问地址错位。我的排查经验是写一个最小的汇编测试程序比如movl $0x12345678, (%eax)然后把NEMU的trace输出和objdump反汇编结果一行行对比哪里对不上就在哪修。另一个常见问题是立即数符号扩展。比如add $0x80, %ebx在32位操作数下0x80应该是正数128但如果你按8位有符号解析当成-128结果就会差一大截。这类问题不会立刻让程序崩溃而是让变量值莫名其妙地不对。我建议在做指令循环时把“操作数宽度”和“是否符号扩展”这两个维度单独写成辅助函数并加单元测试。4.2 上下文切换和虚拟内存的连环坑到了PA2/PA3最大的坑是上下文切换。中断发生时你要在栈上保存完整的寄存器快照恢复时要严格按照保存顺序弹栈。所有寄存器都保存了但eflags没有保存或者保存了eip却用错弹出顺序都会导致用户进程恢复后疯跑。我的做法是画一张“中断栈布局图”把每个寄存器的保存位置和恢复指令写在纸上再对着代码检查。虚拟内存的坑集中在页表。分页开启后CPU访问的每一个地址都会先走MMU转换如果你的页表覆盖不全或者权限位不对NEMU会直接报地址访问异常。这里要特别小心内核线程和用户进程的地址空间切换每次进程切换都需要切换页目录同时还要维护TLB的一致性。在NEMU里通常不需要真的模拟TLB但你必须重新加载CR3。我遇到过一个问题切换页目录后内核代码跑飞后来发现是因为我把内核只映射到了高地址但是在切换CR3之前的代码还在低地址执行跳转失败。4.3 提升调试效率的几个实用手段首先git提交要勤不只在大节点提交每完成一个小功能就应该commit一次。这样改崩了可以随时回退也能看清楚是哪一次改动引入了问题。其次NEMU里的log和trace是调试神器建议在指令执行循环里加一个宏开关#define LOG_ENABLE 1 if (LOG_ENABLE) { printf(eip 0x%x, opcode 0x%02x\n, cpu.eip, opcode); }出问题时打开trace跑一小段程序就能看出执行流在哪里变歪。还有一个技巧是差分测试把你实现的NEMU和QEMU同时跑同一个程序对比执行后的寄存器状态不一致的地方就是bug所在。这个手段对指令集的正确性验证特别有效缺点是QEMU环境本身也要折腾一下但对于后面PA3、PA4的稳定性非常有帮助。5. 这个教学框架的价值远不止一门课5.1 学完之后再看系统底层视角完全不同做完PA之后我最大的变化是看任何程序都会在脑子里多出“它最终变成什么指令”“它落在哪个地址空间”“它调用系统调用时内核做了什么”这几层视角。以前写C语言指针越界只是UB做完PA之后你会意识到越界可能正好写到某个设备映射地址引发全系统崩溃。这种敏锐度对调试线上服务、排查性能瓶颈非常有帮助。对后续学习的影响也很明显再去读OSTEP或者xv6你不会再被抽象概念绕晕因为上下文切换、页表、系统调用这些名词你都是一个一个亲手实现过的。再看QEMU这类开源模拟器的源码也能理解它为什么那样组织代码。甚至去了解RISC-V或ARM的底层时你已经有足够的基础去迁移知识。5.2 这个框架还能怎么扩展项目本身的扩展空间很大。你可以给NEMU增加一个虚拟磁盘设备让NanosLite支持真正的文件持久化也可以把架构从x86移植到RISC-V课程后续版本就是这么做的还可以给NavyApps添加自己的小游戏或工具比如写一个像素画板用AM_GPU_FBDRAW把画面刷到显存立刻就能看到效果。如果你想研究模拟器性能还可以尝试把NEMU的指令循环优化一下加上缓存或动态翻译的雏形。另外如果你有机会当助教也可以基于这个框架设计新的实验题比如让学生实现一个简单的malloc或者给NanosLite加一个优先级调度器。它就像一套“系统积木”你想往哪个方向深挖都能找到对应的接口。最后说一点个人体会。我到现在还记得第一次在NEMU窗口里看到NanosLite启动、仙剑标题画面出现时的兴奋感。这个项目让我意识到所谓“懂计算机系统”不是能背出多少概念而是当一条指令、一次中断、一次内存访问真正按你的设计跑通时你心里有完整的图景。如果时间有限我建议至少把PA1和PA2完整做下来后面的部分会越走越顺。网上那些可以“抄近道”的资料能不看就不看真正的收获都在调试的每一个通宵里。本文还有配套的精品资源点击获取