编程语言研究书库

预备知识 — 一台计算机的最低限度模型

预备知识 — 一台计算机的最低限度模型

这一章讲什么: 一台计算机可以只用三个子系统和三根总线讲清楚; 一个程序从你在编辑器里敲下的字符,到 CPU 真正执行它,中间经过了哪几站。 读完你会拿到全书的「地基层」——后面二十章讲的每一个零件,都装在这层框架里。

1. 顶层全景:一张框图管住全书

先给这一章的全部内容画一张图。这张图也是全书的骨架:

  ┌─────────┐   地址总线(去哪儿)   ┌─────────┐
  │         │ ◄──────────────────► │         │
  │   CPU   │   数据总线(运什么)   │  内存    │
  │         │ ◄──────────────────► │         │
  │         │   控制总线(干什么)   │         │
  └─────────┘ ◄──────────────────► └─────────┘
       │                                   
       └──────────── 三根总线 ────────────► ┌─────────┐
                                           │ I/O 设备 │
                                           │ 键盘/屏幕│
                                           │ 磁盘/USB │
                                           └─────────┘

  图说:计算机 = 三个子系统(CPU、内存、输入/输出)+ 连接它们的三根总线。
  信号在总线上可以双向流动;「总线」既是物理线路,也是一套使用规则(协议)。

原书把计算机硬件明确切成三个子系统:中央处理单元(CPU)、内存、输入/输出(I/O), 三者以总线相连1。

总线(bus)这个词在本书里有两个意思叠在一起:它既是一条物理通信路径, 也是一套协议,规定了这条路径该怎么用。 你把显卡插进主板上的 PCI-E 插槽, 插上去的那组线路就是一条总线;数据什么时候能上去、谁先谁后,就是它的协议。

三根总线各管一件事,名字就是它们的职责2:

总线管什么类比
地址总线指定「内存的哪个位置」或「哪个 I/O 设备」你寄快递时写的门牌号
数据总线运送程序指令和数据本身快递车
控制总线下达「读」还是「写」等命令「送来」还是「取走」的指令

一个容易误解的点:这三根总线是逻辑上的分组,不一定是三根独立的物理线。 原书特别举例:PCI-E 总线上,同一组物理连接会在不同时间先后传送地址和数据信号3。 也就是说,硬件工程师为了省钱省线,可以让一条线分时复用;但逻辑上, 地址、数据、控制三种信息永远分得清清楚楚——这个区分从第 1 章一路用到第 20 章的 I/O。

2. 三个子系统各自干什么

2.1 CPU:管事的

CPU(中央处理单元)是控制者。 它做两件事:对数据执行算术和逻辑运算; 根据运算结果决定下一步做什么4。

注意第二句的分量:「根据结果决定下一步」——这就是「程序能做出判断」的全部 hardware 依据。 if 语句、循环终止、函数返回,最后都会落到「CPU 看了一眼某个运算的结果, 决定接下来取哪条指令」。第 13 章会把这句话展开成一整套控制流机制。

CPU 里还有一小部分容量很小的高速内存(就是后面要讲的寄存器和缓存): 小到什么程度?第 8 章会给一个数:x86-64 CPU 里只有大约 1 KB 的存储空间 是真正归程序员直接支配的5。你写的每个程序,数据的大头都得放在内存里, CPU 只把正在用的那几样攥在手心。

2.2 内存:放东西的

内存为 CPU 和 I/O 设备提供存储,存两样东西:CPU 的指令,和指令要处理的数据。 这一句话里有全书的半个主线——程序本身也是数据:它躺在内存里,和普通数据 用同一种方式被搬运、被存取。

2.3 I/O:对外联络的

I/O 子系统负责与外部环境和大容量存储设备通信:磁盘、网络、USB、打印机都在此列。 I/O 设备比 CPU 和内存慢得多,而且各自的快慢规律千差万别——这一点在 20 章会酿成 一个大麻烦(整个中断机制就是被它逼出来的),第 1 章先埋下这句判断6。

3. 主线例子:一个程序的一生

这一章的主线例子:你在编辑器里写一个最简 C 程序,到屏幕上出现运行结果,中间发生了什么。 挑一个具体输入走全程——程序就用书里的第一个例子那样的 main 函数, 它做的事只是向屏幕写一句话(这个行为的内部细节,第 11 章才展开,这里只看旅程)。

第 ① 站:编辑——源代码是「数据」

你用文本编辑器敲下源程序,存成磁盘上的文件。 关键的一步认知:对这个文件而言,它此刻只是数据。 编辑器并不「懂」它写的东西是一段程序——编辑器把新程序的源代码「视为数据」, 这句话原书在讲编译时又说了一遍7。文件里存的只是一串字符,和一封邮件没有区别。

还要记住一个操作层面的告诫:写程序要用文本编辑器,不能用字处理器 (Word 之类)。字处理器会往里塞隐藏的控制字符来做排版,这些字符会干扰编译器和汇编器, 让它们无法正常工作8。

第 ② 站:编译——还是「数据」

编译器把高级语言(比如 C)的语句翻译成机器代码指令,同样存成磁盘文件。 注意原书的强调:和编辑器一样,编译器也把源代码和最终的机器指令视为数据7。 翻译的过程没有「理解」发生——输入一串字符,按规则换一串别的字符。 这个视角(编译 = 对数据做变换)是整本书后半部分的基调。

书里还交代了这套流程的几个变体9:

变体谁来做机器指令存在哪
编译(本书默认)编译器一次性翻译存成文件,之后随时执行
解释解释器读一句、译一句、立刻执行不存文件
中间路线编译器先译成一种中间速记语言存文件,再由解释器执行存成中间文件(今天很多语言走这条路)

第 ③ 站:载入内存——程序变成「指令序列」

执行程序时,CPU 把磁盘文件里的机器代码指令载入内存,程序里用到的常量数据 一并读入。从此,你的程序就在内存里占据了一块位置,等着被一条一条取走10。

第 ④ 站:取指令——三根总线第一次全部上场

「执行程序」这件事,分解到硬件层,就是 CPU 循环地做同一件事:取指令,执行它, 再取下一条。 原书把取指令的过程写得很细,我们跟着走一遍取「下一条指令」这一步11:

  1. CPU 把「下一条指令在内存中的位置」放到地址总线上;
  2. CPU 把读信号放到控制总线上;
  3. 内存子系统看到地址和读信号,把该位置上的指令放到数据总线上;
  4. CPU 从数据总线复制走这条指令,执行它;
  5. 指令执行完,回到第 1 步——「下一条指令的位置」通常是自动往后挪一格的 (自动增加的那个寄存器叫指令指针,第 9 章细讲)。

写数据到内存是同一个流程的镜像:数据上数据总线、目标地址上地址总线、写信号上控制总线, 内存把数据复制进指定位置12。

读到这里,把三个子系统换成「谁、往哪、干什么」三个问题,这张框图就活了: CPU 始终是发起方;地址总线回答「哪儿」;数据总线回答「什么」;控制总线回答「读还是写」。

第 ⑤ 站:I/O——绕开不碰,但躲不开

你的第一个程序几乎必然要往屏幕上写字。严格做 I/O 编程「不是件容易事, 远远超出了初学者的专业知识范围」——原书原话,它把这块推迟到接近全书的尾声 (第 20 章),中间的所有程序都借助操作系统提供的现成函数来完成 I/O6。

但第 ① 到 ④ 站已经够你看懂一个程序的最小一生了:

  编辑器里的字符 ──编译──► 磁盘上的机器指令文件 ──载入──► 内存中的一串指令
      (数据)                   (还是数据)                      │
                                                              ▼
                                          CPU 循环:地址总线报位置 → 控制总线发读令
                                                    → 数据总线送指令 → 执行 → 取下一条

  图说:前一半是「数据被搬来搬去」,后一半是「指令被一条条取走执行」。
  分界线就是「载入内存」这一站。

4. 编程环境:这本书的「实验室」怎么搭

这一章用一整节交代动手环境。要点不是某个具体软件,而是作者的三个选择, 每个选择都对应他在前言里申明的一个立场:

选择一:Ubuntu 下的 GNU 工具链。 作者用 Ubuntu 20.04 自带的 GNU 编程工具, gcc/g++ 9.3.0、汇编器 as 2.3413。程序稍作修改就能在大多数常用 Linux 发行版里跑。 对应前言的立场:「学习工具应该便宜且容易获得」——整个环境免费下载。

选择二:始终不开优化。 大多数例子的编译选项是 -O0,即不开启优化, 因为「我们的目标是学习概念,而不是生成最高效的代码」14。 这一条值得记住:你拿同样的程序在自己机器上开优化再编译,看到的汇编会不一样; 比较汇编时,编译选项必须先对齐。

选择三:汇编器直接对接 C 运行时。 用汇编写的程序可以调用 C 标准库里的函数, 因为 gcc 在链接时会自动把你的汇编代码和库函数的目标文件链接到一起15。 这一点的直接后果:这本书的汇编程序从第一章起就能 printf——屏幕输出 不必等到你学会 I/O 编程才有。

环境里三级文档,从薄到厚16:

级别命令内容
--helpgcc --help命令行选项速览
手册页(man)man gcc比帮助更完整的文档
infoinfo gccGNU 程序自带的更丰富的文档

5. 作者的判断与证据

这一章是全书唯一一章几乎没有争议的章节——它给的是约定和框架,不是论证。 但有两处值得点出来的「作者的裁决」:

裁决一:只讲三个子系统,其余全部推迟。 真实计算机的硬件结构远比图 1-1 复杂, 作者明确说这张图是「一个公共框架」,目的是让后续讨论有共同的词汇17。 这是教科书式的取舍:先用一个错不了但过度简化的模型建立直觉,再逐章加复杂度。

裁决二:I/O 编程整体后移。 理由是它要求「全面理解设备的工作原理及其与 CPU 和内存的交互方式」,超出初学者范围6。这个推迟是有代价的——中间十章的程序 都要靠 C 库函数这个「黑箱」来做输入输出;好处是主线不被打断。

6. 边界与局限

  • 这是逻辑模型,不是物理描述。 「总线 = 三根线」只在逻辑层成立; 物理上同一条线可以分时传送地址和数据(PCI-E 就是这样)3。
  • 「CPU 循环取指令」的图景省略了缓存、乱序、多核等一切现代加速手段。 第 9 章会提到缓存,但更深层的内容(流水线、分支预测)不在本书范围。
  • 环境描述会很快过时。 Ubuntu 20.04、gcc 9.3.0 是 2025 年中文版印行时的记述13, 今天用更新的发行版,工具和文档的界面会有差异;但「编辑→编译→汇编→链接」 四步流程不变(第 10 章展开)。

7. 可带走的

  1. 计算机 = CPU + 内存 + I/O + 三根总线;总线既是物理路径也是协议, 逻辑上地址/数据/控制永远三分;
  2. 程序也是数据——源代码、机器指令在磁盘上和普通文件没有两样, 「载入内存」是它从「数据」变成「被执行的指令序列」的分界线;
  3. 执行 = 取指令、执行、再取,三个子系统靠「哪儿(地址)/什么(数据)/读还是写(控制)」 三个问题协作;
  4. CPU 的第二职责是「根据运算结果决定下一步」——这是所有判断、分支、循环的硬件根基;
  5. I/O 慢且各自为政,这个事实会逼着全书在末尾长出中断机制(第 20、21 章);
  6. 编译流程的三种变体:编译、解释、中间语言——今天大多数语言处在中间某处;
  7. 用文本编辑器写代码,别用字处理器——隐藏控制字符会干扰编译;
  8. 学概念时关掉优化(-O0),否则你看到的汇编和书里对不上;
  9. GNU 环境三级文档:--help < man < info,一层比一层全。

8. 原文地图

主题原书章原文位置
三个子系统与总线第1 章 预备知识text/05-ch01.txt:16(搜「3 个独立的子系统」) · text/05-ch01.txt:33(搜「地址总线用于指定内存位置」)
总线是逻辑分组、PCI-E 复用第1 章 预备知识text/05-ch01.txt:36(搜「逻辑分组」)
程序执行、取指令流程第1 章 预备知识text/05-ch01.txt:49(搜「提取」) · text/05-ch01.txt:53(搜「地址总线上」)
源代码与机器指令都是数据第1 章 预备知识text/05-ch01.txt:47(搜「视为数据」)
解释器与中间语言变体第1 章 预备知识text/05-ch01.txt:57(搜「解释器是将编程语言转换成机器指令的程序」)
I/O 慢且时序不一、推迟讲授第1 章 预备知识text/05-ch01.txt:61(搜「时序特性差异很大」) · text/05-ch01.txt:63(搜「全面理解设备的工作原理」)
Ubuntu/GNU 工具版本第1 章 预备知识text/05-ch01.txt:78(搜「9.3.0」)
-O0 的理由第1 章 预备知识text/05-ch01.txt:8(搜「们有一个公共框架来讨论事」)
汇编可链接 C 库函数第1 章 预备知识text/05-ch01.txt:66(搜「库函数」)
三级文档 help/man/info第1 章 预备知识text/05-ch01.txt:86(搜「手册页」)
字处理器的隐藏字符第1 章 预备知识text/05-ch01.txt:138(搜「隐藏的控制字符」)

Footnotes

  1. 出处:「第1 章 预备知识」第 16 段(text/05-ch01.txt:16,搜「3 个独立的子系统」)。原文:计算机硬件由「中央处理单元(Central Processing Unit,CPU)、内存(memory)、输入/输出(Input/Output,I/O)」三个独立子系统组成,「三者之间以总线相连」。 ↩

  2. 出处:「第1 章 预备知识」第 33 段(text/05-ch01.txt:33,搜「地址总线用于指定内存位置」)。原文:「地址总线用于指定内存位置或 I/O 设备。程序数据和指令在数据总线上流动。控制总线上的信号指定了各个子系统应该如何使用其他总线上的信号。」 ↩

  3. 出处:「第1 章 预备知识」第 36 段(text/05-ch01.txt:36,搜「逻辑分组」)。原文:总线代表信号「的逻辑分组」;以 PCI-E 为例,「PCI-E 总线上相同的物理连接会在不同的时间传送地址和数据信号」。 ↩ ↩2

  4. 出处:「第1 章 预备知识」第 28 段(text/05-ch01.txt:28,搜「算术和逻辑运算」)。原文:CPU「对数据执行算术和逻辑运算,根据算术和逻辑运算的结果决定操作顺序」,还包含一部分容量很小的高速内存。 ↩

  5. 出处:「第8 章 存储器」第 22 段(text/14-ch08.txt:22,搜「1 KB」)。原文:「在 x86-64 架构中,CPU 中只有大约 1 KB 的存储空间可供程序员使用。」 ↩

  6. 出处:「第1 章 预备知识」第 61 段(text/05-ch01.txt:61,搜「时序特性差异很大」)与第 63 段(text/05-ch01.txt:63,搜「全面理解设备的工作原理」)。原文:I/O 设备「要比 CPU 和内存慢得多,而且时序特性差异很大」;「I/O 设备编程要求全面理解设备的工作原理及其与 CPU 和内存的交互方式」,所以书中程序先用 C 库函数做 I/O。 ↩ ↩2 ↩3

  7. 出处:「第1 章 预备知识」第 47 段(text/05-ch01.txt:47,搜「视为数据」)。原文:「编辑器将新程序的源代码(通常存储为磁盘文件)视为数据」;编译器「和编辑器一样,编译器将源代码和最终的机器指令视为数据」。 ↩ ↩2

  8. 出处:「第1 章 预备知识」第 138 段(text/05-ch01.txt:138,搜「隐藏的控制字符」)。原文:「不要使用字处理器。字处理器会添加很多隐藏的控制字符来格式化文本。」 ↩

  9. 出处:「第1 章 预备知识」第 57 段(text/05-ch01.txt:57,搜「解释器是将编程语言转换成机器指令的程序」)。原文:解释器不是把指令保存在文件中,而是「立即执行」;另一种变体是译成「中间速记语言」,存成「可以由解释器执行的文件」。 ↩

  10. 出处:「第1 章 预备知识」第 49 段(text/05-ch01.txt:49,搜「提取」)。原文:「CPU 将磁盘文件中的机器代码指令载入内存」「通过读取[通常称为提取(fetching)]并执行内存中的指令来运行程序」。 ↩

  11. 出处:「第1 章 预备知识」第 53 段(text/05-ch01.txt:53,搜「地址总线上」)。原文:「当 CPU 准备好执行程序的下一条指令时,该指令在内存中的位置会被放在地址总线上。CPU 还会将读信号放在控制总线上。」 ↩

  12. 出处:「第1 章 预备知识」第 56 段(text/05-ch01.txt:56,搜「写信号」)。原文:「如果 CPU 被指示将数据存入内存,那么它会将数据放在数据总线上,将保存数据的内存地址放在地址总线上,将写信号放在控制总线上。」 ↩

  13. 出处:「第1 章 预备知识」第 78 段(text/05-ch01.txt:78,搜「9.3.0」)。原文:「我使用 Ubuntu 20.04 LTS 桌面版自带的 GNU 编程工具」,gcc 和 g++「版本为 9.3.0,汇编器 as 的版本为 2.34」。 ↩ ↩2

  14. 出处:「第1 章 预备知识」第 8 段(text/05-ch01.txt:8,搜「们有一个公共框架来讨论事」)。原文:「在大多数例子中,我在编译程序的时候没有开启优化(-O0 选项),因为我们的目标是学习概念,而不是生成最高效的代码。」 ↩

  15. 出处:「第1 章 预备知识」第 66 段(text/05-ch01.txt:66,搜「库函数」)。原文:「C 运行时环境(C runtime environment)提供了一个面向应用程序的函数库来访问操作系统的 I/O 函数。」 ↩

  16. 出处:「第1 章 预备知识」第 86 段(text/05-ch01.txt:86,搜「手册页」)与第 89 段(text/05-ch01.txt:89,搜「info」)。原文:大多数 Linux 程序有「手册页」,输入 man 加程序名即可阅读;GNU 程序自带更丰富的 info 文档。 ↩

  17. 出处:「第1 章 预备知识」第 8 段(text/05-ch01.txt:8,搜「公共框架」)。原文:本章目的是「确保我们有一个公共框架来讨论事物的组织形式和结合形式」。 ↩

On this page