简介这份资源是南京大学ICS课程PA实验部分的完整配套材料面向正在修读计算机系统基础课程或希望深入理解计算机底层原理的学生与自学者。内容围绕Nemu虚拟机模拟器、Abstract Machine抽象机、nanos-lite轻量级操作系统内核及Navy-Apps应用展开涉及CPU指令集、内存管理、中断处理、调度算法、设备驱动与Makefile自动化构建等核心知识点适合作为课程设计或课程作业的实践参考。压缩包共487个文件约741KB以C源码与头文件为主体辅以Makefile、汇编、C、Markdown说明及字体、配置等资源结构完整便于按模块查阅。目前已有559人学习下载。通过源码与说明书的对照阅读读者可掌握从抽象机到微型操作系统的实现脉络理解编译链接流程与系统启动配置并借助初始化脚本和项目说明快速搭建实验环境提升系统级编程与工程组织能力。1. 从一份“老派”实验包说起南京大学 ICS PA 到底练什么如果你手里正躺着一个南京大学ICS课程 PA实验部分-内含源码和说明书.zip第一反应大概率是这堆.bdf字体、stb_vorbis.c、lstrlib.c到底跟“操作系统”有什么关系我拆开这份包的时候也有同样的疑问。它其实是南京大学《计算机系统基础》课程里 Project Assignment 的完整工程快照核心不是让你读文档而是让你在 NEMU、Abstract Machine、nanos-lite 这条链路上亲手把“程序如何在裸机上跑起来”这件事走通。适合正在做课程设计、课程作业或者想补计算机系统底层实践的人。它不教你写业务代码它逼你面对指令、内存、中断和 Makefile。下面我按“能复现”的标准把这份资源拆成可执行的步骤。2. 先认清工程骨架NEMU、AM 与 nanos-lite 的依赖关系2.1 三个层次各自负责什么这份 PA 实验包不是单一项目而是三层递进的系统。最底层是 NEMU一个用 C 写的模拟器负责取指、译码、执行并模拟内存和外设。中间层是 Abstract Machine它把不同平台NEMU、native的差异封装成统一的io、mem、trm接口让上层代码不用关心自己跑在模拟器还是真机上。最上层是 nanos-lite一个极简内核提供_start、系统调用和简单的进程调度。你拿到的源码里Makefile负责把这三层按顺序编译、链接init.sh负责启动时的环境准备。理解这个依赖顺序后面改代码才不会“改一层崩三层”。2.2 目录里那些“奇怪文件”的真实用途Courier-*.bdf是位图字体文件NEMU 的 VGA 模拟会读取它们来渲染文字projectn.bmp是启动画面素材stb_vorbis.c是音频解码单文件库用于模拟声卡lstrlib.c是 Lua 的字符串库说明实验里可能嵌了 Lua 做脚本支持。这些不是干扰项而是 NEMU 外设模拟的“素材”。如果你只盯着nemu/src看会漏掉resource/下这些文件对运行结果的影响。常见做法是先把整个目录树打印出来标出哪些是构建产物、哪些是源文件、哪些是运行时资源。# 查看工程顶层结构排除 .git 和 build 目录 find . -maxdepth 2 -type d | grep -v \.git | grep -v build | sort # 查看 Makefile 里定义的目标确认编译入口 grep -E ^[a-zA-Z_-]: Makefile | head -30第一段命令帮你快速看清目录层级避免在错误的子目录里执行make。第二段命令列出 Makefile 的目标你会看到run、debug、clean这类入口。参数上-maxdepth 2控制递归深度太深会刷屏太浅会漏掉nemu/src这种关键路径。如果grep没输出说明 Makefile 用了变量拼接目标名需要直接打开看include的文件。2.3 编译链的入口从make run到第一个指令在 NEMU 目录下执行make run背后发生的事是先编译nemu/src下所有.c链接成可执行文件然后加载nanos-lite/build/nanos-lite.bin作为客户程序最后进入模拟循环。如果你在nanos-lite目录下直接make它只会编译内核不会启动模拟器。这个顺序是很多新手翻车的地方——在错误的目录敲make run报错说找不到目标然后开始怀疑环境。# 在 NEMU 目录下执行确保先编译再运行 cd nemu make run # 如果只想编译不运行用 make 不带参数 make # 清理构建产物换分支或改配置后建议执行 make cleanmake run的隐含依赖是nanos-lite已经编译过。如果报nanos-lite.bin not found先切到nanos-lite执行make。make clean会删除build/下的目标文件但不会动源码。我一般改完Kconfig或Makefile后强制clean一次避免旧对象文件干扰。3. 跑通第一个负载从init.sh到 Navy-Apps 的调用链3.1init.sh不是“初始化脚本”那么简单init.sh在 PA 里扮演的是客户程序入口的角色。NEMU 启动后会先把init.sh加载到内存然后跳转执行。这个脚本里通常包含加载Navy-Apps中某个应用的命令比如hello或timer。如果你直接改init.sh里的应用名就能切换 NEMU 跑哪个负载。但要注意Navy-Apps里的应用需要先编译成.bin并且路径要跟init.sh里的引用一致。# 查看 init.sh 内容确认它加载了哪个应用 cat init.sh # 编译 Navy-Apps 下的所有应用 cd Navy-Apps make # 回到 NEMU 目录重新运行观察输出变化 cd ../nemu make run第一段命令让你看清当前默认负载。第二段编译应用生成build/下的二进制。第三段重新运行如果init.sh里写的应用名和编译产物对不上NEMU 会在加载阶段报错而不是运行时报错。参数上Navy-Apps的 Makefile 通常支持APPxxx指定单个应用但 PA 默认是全量编译省事但慢。3.2 用make debug观察指令流当你发现程序跑飞或者卡死make run只给你一个黑屏或乱码。这时候需要make debug它会启动 NEMU 的调试模式支持单步执行、查看寄存器、设置断点。常见做法是先在_start处设断点然后单步跟几条指令确认 PC 跳转是否符合预期。如果你不熟悉 GDB 风格命令至少记住si单步指令、info r看寄存器、x/10x $pc看内存。# 启动调试模式 cd nemu make debug # 在 NEMU 的调试提示符下输入 # si 4 # 单步执行 4 条指令 # info r # 打印所有寄存器 # x/8x $pc # 查看 PC 指向的 8 个字节si后面的数字是步数不写默认 1。info r的输出里重点看pc、eax、esp。x/8x $pc的x是 examine8x表示 8 个十六进制字。如果pc指向的地址不在预期范围说明前面的跳转指令或栈操作有问题。这个排查路径比盲目改代码有效得多。3.3 参数怎么改从Kconfig到运行时开关PA 的很多行为由Kconfig控制比如是否开启 VGA、是否启用声卡、调试信息等级。改完Kconfig后需要重新make才会生效。另一个常见参数是CFLAGS里的-DDEBUG或-DTRACE它们控制日志输出量。如果你觉得输出太多先关掉TRACE只留DEBUG。我一般会在Makefile里加一个VERBOSE变量默认 0需要时改成 1避免每次手动改代码。# 在 Makefile 顶部加一个开关 VERBOSE ? 0 ifeq ($(VERBOSE),1) CFLAGS -DDEBUG -DTRACE endif?表示如果环境变量没设置就用默认值。ifeq判断后追加宏定义。这样你可以在命令行用make VERBOSE1 run临时开启详细日志不用改文件。参数说明-DDEBUG通常控制Log()输出-DTRACE控制指令级追踪。两者都开会让日志暴涨建议只在定位问题时开。4. 避坑与排查PA 实验里最容易翻车的五个点4.1 现象make run报 “No rule to make target”原因在错误的目录执行或者nanos-lite没编译。PA 的顶层 Makefile 和子目录 Makefile 目标不同run只在 NEMU 目录下有效。解决先cd nemu再确认../nanos-lite/build/nanos-lite.bin存在。如果不存在切到nanos-lite执行make。4.2 现象NEMU 启动后黑屏无任何输出原因init.sh里引用的应用路径不对或者 VGA 初始化失败。先检查init.sh里的文件名是否和Navy-Apps/build/下的产物一致。如果一致用make debug单步跟到 VGA 初始化函数看是否卡在等待垂直同步。解决临时把init.sh改成加载hello应用排除图形输出的干扰。4.3 现象修改Kconfig后行为没变化原因make没有重新生成配置头文件或者旧的对象文件没清理。PA 的构建系统依赖.config和include/generated/autoconf.h。解决执行make clean make确保配置重新生成。如果还不行手动删除include/generated/再编译。4.4 现象调试时si单步卡死原因PC 跳到了未映射的内存区域或者中断没关导致反复触发。用info r看pc值如果不在0x100000附近NEMU 默认加载地址说明跳转异常。解决在跳转指令前设断点检查跳转目标地址的计算是否用了错误的寄存器。4.5 现象Navy-Apps编译报错 “undefined reference to_write”原因Abstract Machine 的接口实现没链接进来或者Makefile里的LIBS顺序不对。PA 的链接顺序要求AM的库在Navy-Apps之后。解决检查Navy-Apps/Makefile里的LDFLAGS确保-lam在对象文件之后。如果用的是ld而不是gcc驱动链接顺序更敏感。5. 进阶技巧用stb_vorbis.c和lstrlib.c验证外设模拟5.1 音频外设的验证路径stb_vorbis.c出现在资源里说明 NEMU 的声卡模拟可能用 Ogg Vorbis 做测试音频。你可以写一个最小客户程序调用 AM 的audio接口播放一段.ogg观察 NEMU 是否输出音频数据。如果 NEMU 没实现音频这个文件就是“预留接口”。验证方法是在Navy-Apps里加一个audio_test调用__am_audio_play然后在 NEMU 的audio模块里打印收到的缓冲区大小。如果打印为 0说明接口没接上。// 在 Navy-Apps 里新建 audio_test.c #include am.h int main() { // 假设 AM 提供了 audio_play 接口 // 实际名称以 am.h 为准 return 0; }上面是骨架具体接口名要查am.h。重点不是代码本身而是通过“调用-打印-对比”确认外设模拟是否完整。如果 NEMU 的audio模块没有实现你会看到链接错误或运行时无输出。这个验证思路同样适用于lstrlib.c——如果实验里嵌了 Lua你可以写一个.lua脚本通过 AM 的文件接口加载看 NEMU 是否能解析。5.2 用lstrlib.c判断 Lua 支持是否启用lstrlib.c是 Lua 的字符串库它的存在暗示 PA 可能支持在客户程序里跑 Lua 脚本。验证方法在init.sh里加载一个.lua文件内容只有print(hello from lua)。如果 NEMU 输出这行字说明 Lua 解释器已经集成。如果没有检查Makefile里是否定义了HAS_LUA之类的宏。常见做法是看nemu/src/monitor下有没有lua目录。如果没有这个文件只是“素材”不影响主流程。5.3 一个我常用的“最小验证”习惯每次改完 NEMU 或 AM 的代码我不会直接跑完整负载。我会先写一个只做一件事的客户程序要么打印一个字符要么读一次时钟。然后make run看输出是否符合预期。如果这个最小程序都跑不通说明底层改动引入了回归。这个习惯帮我省了很多“改了半天发现是环境问题”的时间。从那以后我每次动Kconfig或链接脚本都强制走一遍“最小程序验证”确认基线没崩再跑完整实验。希望帮到你。本文还有配套的精品资源点击获取
