HIT-计算机系统 | 从 Bomb Lab 到 Tiny Shell,我第一次看见程序怎样被机器接住

HIT-Computer-Systems-Labs

计算机系统这门课,给我的感觉一直有点像走进机器的内部。平时写 C 语言时,我们只要关心变量、函数和最后的输出;可一到实验里,程序不再是一段“会运行的文字”,它会变成寄存器里的数、栈上的地址、进程表里的状态,还会因为一个信号没有处理好,让整个终端看起来像突然失控。

这组仓库里有四个实验:数据表示与汇编观察、Bomb Lab 逆向分析、程序优化,以及 Tiny Shell。它们放在一起,刚好组成了一条很有冲击力的路线:先看机器怎样表示数据,再看编译器怎样把程序变成指令,然后试着让代码跑得更快,最后自己写一个能管理前台和后台作业的小 Shell。

仓库地址:KALEsky/HIT-Computer-Systems-Labs。这里记录的是当时的课程实现和历史实验结果,Bomb Lab 的原始二进制没有放进仓库,部分实验也需要旧的课程环境才能完整复现。

实验一:从数据表示开始,数字突然不再只是数字

第一个实验看起来最基础,却是我第一次认真观察“一个数在机器里到底长什么样”。十进制、二进制、补码、移位、位级运算,这些概念以前都在书上出现过,真正把它们和寄存器、汇编指令放在一起时,感觉完全不一样。

在高级语言里写 x >> 2,很容易把它当成一种语法技巧;但当我看到它对应到具体的机器操作时,才意识到这其实是在用另一种方式描述数据移动。负数的右移、符号位怎样扩展、溢出以后留下什么,这些问题不再是考试里的小陷阱,而是程序确实会走到的路径。

这个实验给我的第一个提醒是:抽象很方便,但抽象不是凭空存在的。我们说“整数”“指针”“函数调用”,机器最后都要把它们落到一段段二进制和一组组地址上。后来做 Bomb Lab 和程序优化时,我一直在回头用到这次实验的直觉。

实验二:Bomb Lab,真正让人停下来读汇编的一次作业

Bomb Lab 的目标不是把一个功能写出来,而是面对一个已经存在的程序,想办法把它读懂。程序里有几关,每一关都像在说:如果你没有看清楚输入如何被解析,下一秒就会爆炸。

刚开始我下意识想找“正确答案”,但很快发现这条路走不通。正确输入藏在比较指令、跳转条件、栈布局和函数调用里。你必须先判断这一关在比较什么,再看输入经过了哪些转换,最后才能拼出能通过检查的条件。这个过程不像普通调试那样改一行代码再运行,更像是在黑暗里拿着手电筒,一点点照亮程序的控制流。

我印象很深的一类场景,是明明看见了一个比较指令,却不知道比较的两个值来自哪里。往回跟寄存器和栈,才发现其中一个参数早就在前面的函数调用里被改过了。那一刻我才理解,汇编阅读不是逐行翻译成 C 语言,而是不断追问:这个值从哪里来,它现在代表什么,下一条跳转会把程序带到哪里。

Bomb Lab 也改变了我对“调试”的看法。以前调试更像修理坏掉的代码;这次则是从没有注释、没有变量名的机器指令里恢复出作者的意图。那种读懂之后的感觉很奇怪——程序本身没有变,变的是你已经知道它为什么会在这里跳转、为什么会在这里拒绝输入。

实验三:程序优化,不是把代码写得更花,而是知道瓶颈在哪里

程序优化部分给了我几份不同版本的实现,从最初的循环,到除法替换、循环重排、分块和循环展开。它让我第一次比较具体地看到,所谓“优化”并不是在代码里堆几个看起来高级的技巧,而是围绕数据访问和指令执行反复试探。

最容易理解的是把除以 4 改成右移。这个改动很小,甚至有点像课堂上的技巧题,但它提醒我:某些数学表达式在机器层面有不同的实现成本。真正麻烦的地方,是优化不能只看单行代码。循环顺序调整以后,连续访问的数据可能更符合缓存的习惯;分块以后,数据会被切成更适合局部处理的区域;循环展开则是用更多代码换取更少的循环控制开销。

我当时也有过“代码改得更复杂,速度却没有明显变快”的时候。后来才慢慢接受,优化不是凭感觉,也不是代码越短越好。数据规模、编译选项、缓存、内存访问和测试机器都会影响结果。如果没有基准测试,很多优化只是心理安慰。

仓库里保留了 InitialInitial_2Initial_3BlockPartition 和多种循环展开版本。现在再看这些目录名,它们有点朴素,却很像当时真实的实验过程:不是一下子得到最终答案,而是把每一个想法单独留下来,再比较它们到底有没有带来变化。

计算机系统实验历史结果图

实验四:Tiny Shell,第一次自己处理进程、信号和前后台

Tiny Shell 是这组实验里最像一个“小系统”的部分。它不只是读取一行命令然后执行,还要处理前台作业、后台作业、停止、继续、退出,以及 Shell 自己不能被子进程的信号拖着走。

最基本的流程是 fork 出子进程,再由子进程调用 execve 运行真正的程序。可一旦加入后台任务,事情马上变复杂:父进程什么时候等待?子进程结束后谁来回收?用户按下 Ctrl-C 时,信号应该发给谁?如果一个作业被 Ctrl-Z 停住,再输入 bgfg,它的状态又该怎样更新?

我最初觉得 jobsbgfg 只是几个命令分支,写出来应该不难。真正写到进程组和 SIGCHLD 时才发现,Shell 其实一直在和操作系统抢着处理同一个事实:某个子进程到底还活着、已经暂停、正在后台运行,还是已经退出。

命令表面上做什么背后牵涉到什么
jobs 列出作业 读取并展示作业表
bg 让停止的作业继续在后台运行 更新状态并发送继续信号
fg 把作业切回前台 切换等待对象和前台进程组
quit 退出 Shell 结束主循环并清理状态

Tiny Shell 与进程调度实验结果

这个实验让我真正理解了“信号是异步的”意味着什么。程序不能假设所有事情都会按自己写好的顺序发生。子进程可能在父进程下一行代码执行前就结束,也可能停在那里等用户继续。处理这类问题时,代码的正确性不只体现在返回值,还体现在状态变化的时机。

四个实验连起来,才像一门完整的系统课

如果把这四个实验分开看,内容跨度很大:有位运算,有逆向,有性能,有进程和信号。但它们其实一直在追问同一个问题——高级语言写下的程序,最后怎样被计算机真正执行。

数据表示让我看见数字的底层形态;Bomb Lab 让我沿着指令和控制流反向理解程序;优化实验让我开始关心缓存和内存访问,而不只是“结果对不对”;Tiny Shell 则把进程、信号和操作系统接口放到我自己能控制的代码里。

那种理解不是看完概念就自动拥有的。我是在一次次寄存器值对不上、后台作业没有回收、优化版本没有变快的过程中,才逐渐知道这些概念之间是怎样互相咬合的。

现在回头看,最值得留下的是那些不顺利

这组实验没有让我变成逆向工程师,也没有把 Tiny Shell 变成可以替代 Bash 的工具。它们更像一次短暂的下潜:我从用户态的代码出发,往下看到了汇编、进程和信号,再回到一个仍然不够完善的课程实现。

但这次下潜很有价值。以后再遇到一个“明明代码没问题,运行却不对”的情况,我不会只盯着业务逻辑,也会问问自己:是不是数据表示错了?是不是调用约定没理解?是不是缓存和内存访问造成了差异?是不是一个异步信号改变了状态?

这就是我现在愿意保留这个仓库的原因。它不完美,部分实验也不能在现代环境中一键运行,可它把我第一次真正靠近计算机系统的过程留了下来。

仓库地址:HIT-Computer-Systems-Labs

说明:本文根据个人历史实验材料和公开仓库整理,部分文字经过 AI 辅助润色;Bomb Lab 的原始课程二进制未随仓库公开,文中结果属于历史实验记录。课程资料仅用于学习回顾和个人作品整理,不用于当前课程作业或考试。

posted @ 2026-09-10 21:25  何以牵尘  阅读(3)  评论(0)    收藏  举报