在南大计算机系的大一新生之中一直流传着一个神秘的传说, 它的名字叫 ICS PA.
其实是大二计算机系统基础课程的一份难度相对较大但设计的很好的课程大作业.
目标是实现一个”青春版” QEMU – Nemu, 实现 ALU, FPU, x86 指令集, 保护模式, 内存 cache / 分页 / 分段, 外设 IO 功能的虚拟机, 并运行一些简单的程序 (简化了很多实现, 现代的 OS 肯定是跑不起来了…).
具体需要的实验内容和指导打包放在这里: https://pan.lyc8503.net/Public/%E5%8D%97%E5%A4%A7/PA/ICS.rar
GitHub 上也有: https://github.com/ics-nju-wl/icspa-public-guide
刚听说的时候感觉工作量还挺大的, 毕竟 x86 指令集本身也不简单…
但后来有同学找我做做看, 我就欣然接受了这个支线任务, 并且速通了它的一周目.
写一下 实验报告 游戏通关记录.
实验心得
省流: 真的是一份高质量的作业, 想了解 x86 指令集和计算机工作原理的都可以来玩玩看~
从 2022-3-14 晚上拿到这份 PA 开始就觉得很有趣, 越做越上头(雾), 正好由于疫情缘故学校转了线上上课, 可以专心敲代码, 一路做了下去肝到 2022-3-19 凌晨就把它做完了. 算是这份 PA 的一周目通关. 4天做完了一学期的作业.
其实工作量没有想象中的大, PA 提供了虚拟机基本的框架和指引, 并且为每部分设计了测试用例. 基本就是按着要求做一些代码的”完型填空” + 查 80386 Manual 的循环, 减少了很多 debug 和重复性琐碎劳动的工作量.
计算机的历史不算久远, 但发展迅速, 技术迭代和淘汰的速度也很快, 技术栈不断更新, 背后支撑起整个庞大体系图灵机却一脉相承, 能花不太多的时间一窥计算机底层的原理, 体验一下计算机界前辈们走过的路, 看到当年的软硬件在现在留下的影子, 也是一次很奇妙和新奇的体验.
一份设计优秀的作业能帮你快速学习知识提升能力, 可惜这样的作业并不常见. 更多的时候只是老师上课念念 PPT 课后随便出几道题. 不知道能不能说是在互联网大行其道的当下, 还愿意做系统的老师和助教会更加踏实呢?
做完 PA 也学到了一些东西:
单元测试真的很重要, 特别是对 C. 虽说写 Testcase 很无聊, 但自己写 OS 的时候懒得写测试用例的后果就是一个小 Bug 变成一串离奇的 Bug, 然后一个 bug 改一天. 同样地, 多用 assert 提早暴露错误.
对 x86 指令集有了更深入一些的了解, 毕竟直接让我硬读 80386 手册我肯定是懒得读, 写代码更有趣.
自主学习的能力, 查找资料的能力, 使用正确工具的能力, etc. 这些以前就有了, 要不然怎么做这么快.( 最多算是再练习了一下.
踩过的坑
PA0 (果然配环境可以消磨你学习新知识 90% 的热情)
主机要求 32bit 机器, 顺手装了 Debian 11 i836 (Flag), CLion 和 vsc 在 32bit 机器上都装不了… CLion 的 Remote Host 要求使用 cmake, 而项目用的是 Makefile… PA 本身推荐使用 vim 编程, 但我还是不太习惯, Sublime 倒是可以装但是 VNC 的体验也不怎么样.
一番纠结折腾之后选择直接挂载远程磁盘到本机, 用 CLion 的 Lightedit Mode 敲代码.
PA1-1 & PA1-2 (寄存器/ALU)
无, 读读文档照着写就愉快的过了.
PA1-3 (FPU)
要求不能转 float 直接算, 在实现规格化浮点数的时候被那个隐藏的 ‘1’ 坑了一个小时.
PA2-1 (指令集)
首先需要特制的 objdump
用于调试, 然而提供的二进制文件
还好助教提供了一些宏和整体的框架, 要不然就得复制黏贴一大堆了. 引用 PA 文档中的评价: 这种大量代码克隆的方法虽然挺快也挺容易理解的,但会导致我们工程的代码非常臃肿。更严重的是,如果等我们都拷贝完了,发现最初的 add_r2rm_b 函数中有一个小小的 bug,岂不是要把这个过程再重复一遍?可见此法 Stupid 有余而 Simple 不足,不符合 KISS 原则。
使用框架的时候还是踩了一些坑, 比如忘记调用 operand_read
, 复制的时候忘记改, 对于某些参数特殊的指令因为不好好读文档卡了半天…
总体来说 PA2 要实现很多指令, 还是最繁琐的一部分, 只不过实现完看它跑起来还是挺爽的.
PA2-2 (ELF Loader)
PDF 里给出了明确的指引, 也是愉快通过了, p.s. 终于引入 Kernel 了.
PA3-1 (内存 L1 Cache)
愉快写完所有 testcase 通过.(Flag)
PA3-2 (保护模式)
因为工作在扁平模式而且没有模拟特权等级所以有错也不知道. Testcase 过了个寂寞.
PA3-3 (内存分页)
之前给自己挖坑, 现在遭报应了…
其实这部分本身不难, 但是先是由于装了 Debian11 (gcc-10)(Flag 回收) 产生了一些诡异的行为, 使用编译参数 -Ttext=0xc0030000
后使用 objdump
查看 bss 区地址的确在高于代码区的位置, 但是代码运行过程中获取到全局变量的地址只是 0x0005xxxx
, 然后助教提供的 kernel 就炸了, de 了整整三小时 bug 才找出问题… 后来尝试使用自己的程序复现, 却复现不出, 可能是 Makefile 中
之后又是 L1 Cache 写错了**(Flag 再次回收)**, 结果由于 PA3-1 的 Testcase 不够强居然都通过了… 然后导致 kernel 在调用 mm_malloc
时导致 kernel panic, 然而助教并没有给出 mm_malloc
的代码根本没法 debug.(
又搞了三小时终于一点一点排除出了 L1 Cache 的问题.(就是 write through 误判了导致本来 cache miss 变成了 cache hit…)
后来就成功通过了.
PA4-1 & PA4-2 (中断 / IO / 内存映射)
大部分硬件模拟代码其实助教都给出了, 按照手册实现一下一些相关命令基本上就完成了~
完结撒花~✿✿ヽ(°▽°)ノ✿
一些吐槽
- 以前我一直嫌弃没有 kvm 的 QEMU 太慢, 直到我写的精简版比它还慢亿点点.(
- 虽然一直传说这个 PA 很难, 但相比自己从零开始查 Manual 架构和 Coding, 可以说是保姆级教程了…
- x86 指令集真的好复杂… Intel 做 CPU 真不容易.(奇怪的关注点.) 但这也不应该是它 14nm+++++++++++ 的借口.
- 本来还想把我写的 OS 移植到上面运行的, 结果还有好多功能没实现跑不起来, 就咕咕咕了. 如果真移植了恐怕是 Bug 超级加倍, 一个 Bug 从用户程序一路找到 kernel 找到 CPU 那工作量就…
- 最烦的是 PA2, de 了最久 bug 的却是关于内存的 PA3, 突然联想到组装主机的时候一些奇奇怪怪的不稳定的问题也很有可能是内存引起的, 测试用例里应该加个 memtest…
本文采用 CC BY-NC-SA 4.0 许可协议发布.
作者: lyc8503, 文章链接: https://blog.lyc8503.net/post/nju-ics-pa-season1/
如果本文给你带来了帮助或让你觉得有趣, 可以考虑赞助我¬_¬