计算机组成原理(基于 x86-64 架构) — 全书拆解¶
30 秒导读: 这是一本写给程序员的组成原理教材,回答一个问题: 你在高级语言里写的一行代码,在机器层到底发生了什么? 它的讲法是一条完整的因果链:开关 → 位 → 数 → 算术 → 逻辑 → 门 → 电路 → 存储器 → CPU → 指令 → 汇编 → 函数 → 数据结构 → 浮点 → I/O → 中断。 全程不用假想的教学处理器,只用真实的 x86-64 指令和 GNU 工具链——每章都能在自己 电脑上复现。
本组文档是我们重写的完整拆解,二十一章。读完不必看原书。
1. 先交代清楚:这是谁在什么时候写的¶
| 作者 | 罗伯特·G.普兰茨(Robert G. Plantz)——曾为双子星座号飞船和阿波罗登月舱设计电子设备,伯克利电气工程博士,做过 8 年软件工程师,后任教于索诺玛州立大学 20 余年1 |
| 中文版 | 门佳、朱西方 译,人民邮电出版社 2025 年 |
| 内容组织 | 作者自述分三部分:数学和逻辑 → 硬件 → 软件;所有编程在 Ubuntu 的 GNU 环境(gcc 9.3 / as 2.34)下完成 |
| 写作立场 | 前言里明说:目标不是培养汇编专家,而是让你成为更优秀的程序员——书里的汇编是理解工具,不是生产工具1 |
两件事读之前必须知道:
第一,全部示例用 -O0 编译。 作者在前言里交代,为的是「学概念,不是生成最高效的代码」。 这意味着你看到的汇编是"最忠实于 C 语义"的版本,而不是真实世界的优化输出—— 两者差多少,各章的「边界与局限」里都有提示。
第二,这是一个「教学子集」。 x86-64 约 1500 条指令,作者只选了讲清概念必需的一小撮, 理由同样写在前言:别的书靠"理想化指令集"回避指令太多的问题,这本书宁可用真指令的真子集1。
2. 全书一条主线(读完这一节,你就懂了这本书)¶
这本书是一条从头贯到尾的推理链:每一层都是被上一层逼出来的。 只读这一节,你也能把这本书讲给别人听。
① 计算机只有三种东西¶
所有计算机硬件 = CPU + 内存 + 输入/输出,三根总线相连(01)。 程序 = 存在内存里的一串指令;CPU 的工作就是取一条、执行一条,再问下一条在哪。
② 但"存在内存里"之前,得先回答:用什么记?¶
内存里只有双态开关。用位记开关状态,8 位一字节,十六进制是人看的速记(02)。 开关记数字没有算术,所以先立规矩:固定位数,溢出和进位天生存在(03); 开关要表达"条件",所以引入布尔代数——真和假能算,还能化简(04)。
③ 代数要有身体:门与电路¶
布尔函数怎么变成物理?晶体管造逻辑门(05),门组成电路: 没有记忆的(加法器、译码器)叫组合逻辑(06), 把输出喂回输入就能"记住"的(锁存器、触发器)叫时序逻辑——存储就是这么来的(07)。 锁存器阵列 → 寄存器、缓存、内存,越近 CPU 越快越贵,组成金字塔(08)。
④ 现在,看一颗真正的 CPU¶
子系统齐备:指令指针、指令寄存器、控制单元、ALU、寄存器堆(09)。 CPU 一生只做一件事:取指 → 译码 → 执行 → 再取指。
⑤ 从 C 到机器:一条流水线四个岗¶
gcc 四步:预处理 → 编译(生成汇编)→ 汇编(机器码)→ 链接(填地址)(10)。
你写的高级语言,到这一步已经全部变成字节。从此每章都在拆这些字节。
⑥ 函数:程序的真正组织单位¶
参数经寄存器传(前 6 个),局部变量住栈帧,返回地址压在栈上,规矩叫 System V ABI(11)。 指令本身也只是一串字节:操作码 + ModR/M + SIB + 偏移量,CPU 靠字段分解找回操作数(12)。 循环和分支到机器层只剩「改 rip」:跳回去是循环,跳过去是分支(13)。 函数间的数据流动只有四条路:全局、按值、按指针/引用、返回值; 第 7 个起参数走栈,正偏移读参数、负偏移用变量——17 条规则收拢全部协议(14)。 递归没有新机制,只是一串栈帧自己套自己(15 前半)。
⑦ 算得动,才算得好¶
位掩码改写单个位,移位乘除 2 的幂,imul/div 的隐式寄存器约定是 x86 的传统艺能(16)。 数组的索引、结构体的字段名,机器层全是「基址 + 偏移量」(17); 对象 = 记录 + 一个藏起来的 this 参数 + 改名后的函数(18)。
⑧ 数与外界的麻烦¶
小数有两条路:定点(换范围)与 IEEE 754 浮点(换精度),后者不是实数, 舍入/吸收/抵消三种误差各配了 gdb 实录(19)。 I/O 设备比内存慢几个数量级且脾气各异,于是有了轮询、中断、DMA, 以及端口映射与内存映射两种够到设备的方式(20)。
⑨ 收尾:操作系统凭什么管得住一切¶
靠特权级(0 级内核 / 3 级程序,内存访问按级别把关)和中断/异常机制 (保存现场 → 查门描述符表 → 升级 → 跳转)。 你平时调的每个 C 库 I/O 函数,底层都是一次从 3 级到 0 级的系统调用—— 书的最后一个程序,就是不用任何库、直接 syscall 的 Hello, World!(21)。
⑩ 一句话压缩¶
高级语言的每一行,都是开关序列的一层糖衣;这本书做的, 就是把每一层糖衣剥给你看,并让你看到糖衣之所以这么包,是被下一层的硬约束逼的。
3. 二十一章地图¶
这张表不用记任何术语——每章名字后面写的是「读完你能回答什么」。
| 章 | 读完你就能回答 |
|---|---|
| 01 预备知识 | 一台计算机最少由哪几块组成?程序从磁盘到执行走了哪几步? |
| 02 数据存储格式 | 开关怎么记数字和字母?「123abc」在内存里到底长什么样? |
| 03 计算机算术 | 为什么整数会"溢出"?补码是什么,为什么非用它不可? |
| 04 布尔代数 | "真和假"怎么变成能化简的代数?卡诺图凭什么能化简电路? |
| 05 逻辑门 | 晶体管怎么造出与/或/非门?为什么说 NAND 是"万能门"? |
| 06 组合逻辑电路 | 加法器怎么一位一位进位?译码器和复用器是干什么的? |
| 07 时序逻辑电路 | 电路怎么"记住"一个位?寄存器和状态机是怎么造出来的? |
| 08 存储器 | 为什么既要有内存又要有缓存?寄存器到云,速度和价格怎么排? |
| 09 中央处理单元 | CPU 内部有哪几个部门?"取指-执行"循环具体怎么转? |
| 10 汇编语言编程 | 一个 .c 文件怎么变成可执行文件?每一步的产物长什么样? |
| 11 深入 main 函数 | 参数怎么进寄存器?局部变量住在哪?return 怎么找到回家的路? |
| 12 剖析指令 | 一行汇编怎么编码成字节?CPU 怎么从字节里找回操作数在哪? |
| 13 控制流结构 | while/if/switch 在机器层各长什么样?「高于」和「大于」为什么必须分清? |
| 14 剖析函数 | 第 7 个参数起去哪了?「画栈帧图」为什么是调试函数的第一动作? |
| 15 函数的特殊用法 | 递归在机器层有新机制吗?C 读不到溢出标志,汇编怎么读? |
| 16 位、乘除 | 0.1+0.2 的整数版问题是什么?div 之前为什么必须先清 rdx? |
| 17 数据结构 | a[i] 和 x.field 在机器层是同一种东西吗?结构体的"填充"从哪来? |
| 18 面向对象 | "对象"在汇编层是什么?this 指针藏在哪?编译器删了你的构造函数会怎样? |
| 19 小数 | 为什么 0.1 存不精确?浮点误差的三种长相各是什么? |
| 20 输入/输出 | 为什么 I/O 比内存难得多?轮询、中断、DMA 各解决什么? |
| 21 中断与异常 | 操作系统凭什么拦得住程序?一个不用 C 库的 Hello World 怎么写? |
推荐顺序: 01 → … → 21,这也是原书的推理顺序,不建议跳。 硬件部分(01–09)和软件部分(10–21)可以分开读,但 09(CPU)与 10(编译流水线)之间不要断。
读汇编清单的通用姿势¶
给一套读任何编译器汇编产出的通用顺序——后面十九章都适用:
- 先找序言和结语:push rbp / mov rbp, rsp 在哪、结语 mirror 在哪, 函数边界就清楚了;
- 再看常量区:.rodata 里的字符串和数字,是程序的"原料清单";
- 然后找调用:call 指令标出了程序的结构骨架——每个 call 就是一次"求人办事";
- 最后看控制流跳转:jmp/jcc 画出的箭头,是程序的实际走法。 C 源码里的结构(if/循环)到这一层全部变成这些箭头。
这个顺序反过来也成立:拿到一段陌生汇编,先找 call 和 jmp,大致结构就出来了—— 比从头逐行读快得多。本章的所有清单,都可以用这个姿势再过一遍,当作练习。
4. 这本书覆盖什么、不覆盖什么¶
覆盖(而且讲得比多数教材清楚)——读完你能回答:
- 一行 C 从源代码到机器码经过哪四步,每步产物(.i/.s/.o)怎么查看;
- 函数调用的完整协议:寄存器传参、栈帧、红区、第 7 个参数、17 条规则;
- 递归、栈金丝雀、名称重整、this 指针——从 gdb 实录里看清,而不是背定义;
- 整数乘除的隐式寄存器约定(div 前清 rdx)、有符号除法的 C/Python 规则差异;
- IEEE 754 的格式与三个真实误差实验(舍入、吸收、抵消、不结合性);
- 中断/异常/系统调用的机制轮廓,以及不依赖 C 运行时的最小程序。
不覆盖(别指望在这本里找):
| 缺什么 | 说明 |
|---|---|
| 多核与现代微架构 | 乱序执行、分支预测、流水线细节、缓存一致性——本书是单核视角 |
| 虚拟内存的完整故事 | 只讲了页表/MMU 的概念轮廓,分页机制、TLB、置换算法都没展开 |
| ARM/RISC-V 等其他指令集 | 全书只基于 x86-64 |
| 性能工程 | 提到缓存重要,但没有体系化的分析与优化方法 |
| 并发与网络 | 多线程、锁、协议栈不在范围 |
| C++ 的深度 | 虚函数、继承、多态、异常(被 -fno-exceptions 关掉了)都没讲 |
5. 我们的判断¶
判断(我们的,不是书里的): 这本书今天的价值排序是—— ① 函数协议与栈帧教学(11–14 章,至今少有对手)> ② 用真实工具链教概念的方法论 > ③ 硬件部分(01–09,合格但传统的教法)> ④ 浮点与 I/O 两章(方向对、深度浅)。 如果你的目标是「读懂编译器输出、调得了段错误」,这本比经典的 CS:APP 更薄、更直接; 如果要性能优化与系统编程,它是地基,不是楼房。 如果错,会错在: CS:APP 的实验体系(炸弹实验、Attack Lab)在动手深度上其实更强, 我们只是以「读得懂」为判据加权;换「练得会」为判据,排序可能反转。
判断(我们的,不是书里的): 全书最值得带走的是一种读法: 每遇到一个抽象,问三个问题——「它的机器层形状是什么?」「它被哪个硬约束逼成这样?」 「编译器/工具链能怎么验证它?」(本书三件套:gcc -S 看输出、gdb 看寄存器内存、 as -al 看机器码)。这套三问对任何新语言、新框架都通用。 如果错,会错在: 解释型语言和托管运行时(虚拟机、GC)把「机器层」隔得更远, 三问的第一问有时答不出来——但它依然有效,只是答案从"汇编"变成了"字节码/运行时语义"。
判断(我们的,不是书里的): 注意作者的边界感——几乎每个深坑 (ModR/M 全表、链接器细节、中断处理程序、浮点 fenv)他都只走到「能查、能读」就停, 并且明说不讲。这不是偷懒,是教材最难的部分:范围的纪律。 对照组是试图什么都讲的教材,读者往往什么都没留下。 如果错,会错在: 有些读者需要的恰恰是其中某个深坑的全部细节—— 对那批人,这本书的正确用法是「地图 + 手册指针」,而它给的手册指针 (Levine、Tanenbaum、Goldberg、fenv)恰好够用。
6. 许诺兑现表¶
| 许诺在哪 | 应兑现在哪 | 核过了吗 |
|---|---|---|
| 本页 §2 ⑤「从此每章都在拆这些字节」 | 12(指令字节)、16(乘除编码) | ✓ |
| 本页 §2 ⑥「17 条规则收拢全部协议」 | 14 §5 末的协议总表 |
✓ |
| 本页 §2 ⑨「最后一个程序」 | 21 §3 主线例子 |
✓ |
各章内部的「第 N 章讲」均在当章或目标章的原文地图中可回核,不再逐条登记。
拆解依据 2025 年中文版(人民邮电出版社)。原始书籍文件不入库,本组文档是我们自己的重写;
每条引用都可用 node scripts/book-verify.mjs ji-suan-ji-zu-cheng-yuan-li 回核。