书 · 2005-06 · 诺姆·尼桑(Noam Nisan)、西蒙·肖肯(Shimon Schocken)

《计算机系统要素》

计算机与 AI书单第 8 名

一句话结论

亲手把逻辑门、处理器、编译器和运行时服务接起来,你就能理解高级程序究竟怎样变成机器的动作。

值不值得读完整本?

值得读完整本,并配合项目动手做。它最有价值的地方,是把通常分开学习的硬件和软件连成一条可以追踪、可以运行的链。

  • 先读序言和第1—3章,掌握接口、组合逻辑与时序逻辑的区别;配合完成基本逻辑门、加法器和寄存器练习。
  • 第4—6章要连着读:先理解机器指令,再看处理器怎样执行它,最后实现汇编器。重点追踪一条指令怎样改变寄存器、内存和程序计数器。
  • 第7—8章的栈、内存段和函数调用约定,以及第9—11章从Jack语言到VM代码的编译过程,是软件部分的重点。建议至少完整实现一次函数调用翻译和一个小程序的编译。
  • 第12章值得细看,用它理解算术、内存分配和输入输出服务怎样支撑应用。有经验者可略读熟悉的真值表推导和工具操作说明;附录按需查阅,第一次阅读不必逐页读完。

核心观点

1. 把抽象接口和具体实现对上,才能理解整个系统

计算机可以拆成一层层提供服务的模块,每层使用下面一层,并向上面一层隐藏细节。作者让读者既学习这些接口,又实现接口背后的机制。

为什么:如果同时考虑所有电路和软件细节,人的注意力很快就会不够用。明确的接口让我们暂时只关心一个模块接收什么、保证什么,从而把大问题拆成可处理的小问题。亲手实现模块,又能揭开接口背后的过程,避免把抽象当成魔法。只要不同实现遵守同一接口,就能替换、单独测试,再逐步组合成系统。

2. 算术能力可以由简单逻辑逐层组合出来

计算机的基本运算不需要为每个问题设计一种全新的装置。布尔逻辑、二进制表示和可复用的电路部件,足以构造出算术逻辑单元。

为什么:组合逻辑可以用输入与输出之间的布尔函数描述,而NAND门具有构造其他布尔运算的能力。二进制加法可以拆成每一位的求和与进位,因此小型加法部件能够连接成多位加法器。补码表示让固定字长的有符号加减法能够共享大量电路。再用控制信号选择输入处理方式和运算结果,一个ALU就能承担多种算术与逻辑任务。

3. 存储状态与指令控制,让电路成为可编程计算机

只有根据当前输入产生输出的电路还不够;计算机还必须保存状态,并按指令决定下一步做什么。寄存器、内存、程序计数器和CPU把这种能力连接起来。

为什么:时序部件让信息能够跨越时钟周期保留下来,后续计算因此可以依赖先前的结果。寻址机制允许同一套读写电路操作不同的存储位置。CPU解释指令中的控制位,选择ALU运算、写入目标和跳转条件。程序计数器决定下一条执行的指令,因此更换程序就能改变机器行为,而不必重新连接硬件。

4. 分层翻译把程序表达与具体机器连接起来

高级程序经过VM代码、汇编代码和机器码等表示,最终成为硬件能执行的指令。不同翻译层各自解决一部分问题,使语言设计与机器实现不必绑在一起。

为什么:汇编语言用助记符和符号名称代替难以维护的二进制编码,由汇编器负责解析地址并生成机器码。VM进一步提供栈运算、内存段和函数调用等统一操作,让编译器不必直接处理目标CPU的寄存器细节。VM翻译器再把这些操作落实为具体机器上的指令序列。如果目标机器改变,保持VM约定不变,就可以主要调整后端翻译部分,而保留上层编译工作。

5. 编译器靠语法、名称绑定和代码生成落实高级语言

变量、表达式、循环和对象都必须被转换成明确的数据位置与执行步骤。编译器通过分析程序结构、记录符号信息,再按规则生成低层代码来完成这件事。

为什么:词法分析先把字符流切成有类别的词元,语法分析再确定这些词元如何组成表达式和语句。语法结构说明程序的组成关系,却不能单独确定一个变量存在哪里,因此还需要符号表记录变量类别和索引。代码生成利用这些信息,把表达式变成栈操作,把控制结构变成标签和跳转。这样,高级语言提供的便利就有了可执行的实现,而不需要CPU直接理解循环或对象。

6. 运行时服务补上语言与硬件之间的能力缺口

编译完成并不意味着应用所需的能力都已具备,还需要内存分配、数学运算、字符串和输入输出等公共服务。书中的Jack操作系统主要承担这些基础服务。

为什么:硬件只提供有限的原始操作,应用却需要更方便的功能,例如分配一块内存或在屏幕上显示字符。如果每个应用都自行实现这些功能,代码会重复,也容易出现不一致的行为。把它们集中成具有明确接口的服务,编译器和应用就能通过调用来复用。复杂功能因此可以由简单机器指令上的算法实现,而不必全部加入硬件指令集。

反对意见

运行成功不等于理解并证明正确(艾兹赫尔·戴克斯特拉)

戴克斯特拉在《On the cruelty of really teaching computing science》中主张,入门教学应训练学生从形式化规格出发,同时构造程序与正确性证明;测试不能证明一般程序没有错误。这与本书强调构建、运行和测试的教学重心不同:项目通过了,仍可能没有覆盖某些输入或状态。这是教学立场的对照,并非他对本书的评论。

抽象不能完全隔绝底层代价(乔尔·斯波尔斯基)

斯波尔斯基在《The Law of Leaky Abstractions》中指出,复杂抽象总会在某些情况下暴露底层限制,例如内存访问顺序会影响性能,网络故障会突破上层接口的便利。他的提醒是:接口相同、功能正确,不代表性能和故障行为也相同。因此,本书展示的清晰分层适合理解基本机制,实际工程仍需要跨层排查问题。这篇文章并非针对本书。

理解系统也可以从真实程序的行为出发(布莱恩特、奥哈拉伦)

布莱恩特和奥哈拉伦在《深入理解计算机系统》中明确选择程序员视角,强调通过真实机器上的程序理解表示、内存层次、链接、进程和并发。这提供了不同的学习路径:若目标是改善现实程序的性能与可靠性,从真实系统观察行为可能比先构造一台教学计算机更直接。这是学习目标与路径的分歧,不是两位作者对本书的直接批评。

相关推荐

导读由 AI 根据公开资料整理,没有读取原书全文,一切以原著为准。导读 Primer 首页