<audio id="audio" title="03 | 汇编：编程语言的诞生" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/94/7a/94bf6a870c9492ef4cc327807ea0587a.mp3"></audio>

你好，我是七牛云许式伟。

在上一讲中，我们一起解剖了架构大厦的地基：冯·诺依曼体系。接下来，我们就开始沿着这座大厦攀登，一起来聊聊编程语言。

对于现代计算机来说，虽然 CPU 指令是一个很有限的指令集，但是CPU 执行的指令序列（或者叫“程序”）并不是固定的，它依赖于保存在存储中的数据，由软件工程师（或者叫“程序员”）编写的软件决定。

从上一讲中，我们可以知道，计算机的程序可能被保存在计算机主板的ROM上（这段程序也叫计算机的启动程序），也可能被保存在外置的存储设备（比如硬盘）上，并在合适的时机加载执行。

程序称得上是计算机的灵魂。指令序列的可能性是无穷的，程序的可能性就是无穷的。今天计算机创造的世界如此多姿多彩，正是得益于程序无穷的可能性。

那么，软件工程师是怎么编写程序的？

## 编程的史前时代

在第一门面向程序员的编程语言出现前，人们只能通过理解CPU指令的二进制表示，将程序以二进制数据方式刻录到存储（比如ROM或硬盘）上。

这个时期的编程无疑是痛苦的，效率是极其低下的：且不说我们怎么去修改和迭代我们的程序，光将我们的想法表达出来就极其困难。

我们首先要把表达的执行指令翻译成二进制的比特数据，然后再把这些数据刻录到存储上。

这个时候软件和硬件的边界还非常模糊，并不存在所谓软件工程师（或者叫“程序员”）这样的职业。写程序也并不是一个纯软件的行为，把程序刻录到存储上往往还涉及了硬件的电气操作。

为了解决编程效率的问题，汇编语言（和解释它的编译器）诞生了。汇编语言的编译器将汇编语言写的程序编译成为CPU指令序列，并将其保存到外置的存储设备（比如硬盘）上。

汇编语言非常接近计算机的CPU 指令，一条汇编指令基本上和CPU指令一一对应。

## 与机器对话

汇编语言的出现，让写程序（编程）成为一个纯软件行为（出现“程序员”这个分工的标志），人们可以反复修改程序，然后通过汇编编译器将其翻译成机器语言，并写入到外置的存储设备（比如硬盘）。并且，程序员可以按需执行该程序。

在表达能力上，汇编语言主要做了如下效率优化。

<li>
用文本符号（symbol）表达机器指令，例如 add 表示加法运算，而不用记忆对应的 CPU 指令的二进制表示。
</li>
<li>
用文本符号（symbol）表达要操作的内存地址，并支持内存地址的自动分配。比如我们在程序中使用了“Hello” 这样一段文本，那么汇编编译器将为程序开辟一段静态存储区（通常我们叫“数据段”）来存放这段文本，并用一个文本符号（也就是“变量名-variable”）指向它。用变量名去表达一段内存数据，这样我们就不用去关注内存的物理地址，而把精力放在程序的逻辑表达上。
</li>
<li>
用文本符号（symbol）表达要调用的函数（function，也叫“过程-procedure”）地址。对 CPU 指令来说，函数只有地址没有名字。但从编程的角度，函数是机器指令的扩展，和机器指令需要用文本符号来助记一样，函数的名称也需要用文本符号来助记。
</li>
<li>
用文本符号（symbol）表达要跳转的目标地址。高级语言里面，流程控制的语法有很多，比如 goto、if .. else、for、while、until 等等。但是从汇编角度来说，只有两种基本的跳转指令：无条件跳转（jmp）和条件跳转(je、jne)。同样，跳转的目标地址用文本符号（也就是“标签-label”）有助于程序逻辑的表达，而不是让人把精力放在具体的指令跳转地址上。
</li>

总结来说，汇编从指令能力上来说，和机器指令并无二致，它只不过把人们从物理硬件地址中解脱出来，以便专注于程序逻辑的表达。

但是，这一步所解放的生产力是惊人的，毕竟如果有选择的话，没有人会愿意用0101这样的东西来表达自己的思想。

## 可自我迭代的计算机

从探究历史的角度，你可能会期望了解最真实的历史发展过程。比如：怎么产生了现代计算机（以键盘作为输入，显示器作为输出）？怎么产生了汇编语言？怎么产生了操作系统？

不过，本专栏是以架构设计为目的，我们目的并不是还原最真实的历史。架构的意义在于创造。我们甚至可以设想一个有趣的场景：假设今天我们的信息科技的一切尚不存在，那么从架构设计角度，我们从工程上来说，如何更高效地完成从0到1的信息科技的构建？

> 
最早的输入输出设备并不是键盘和显示器，而是打孔卡和打印机。用打孔卡来作为机器指令的输入，早在18世纪初就被用在织布机上了。早期的数字计算机就是用打孔卡来表达程序指令和输入的数据。
下图是 IBM 制造的打孔卡：


<img src="https://static001.geekbang.org/resource/image/90/99/907ef3466d15146c8aa1b2ea2a7dbd99.png" alt="">

我们可以想象一下，第一台以键盘+显示器为标准输入输出的现代计算机出现后，一个最小功能集的计算机主板的ROM上，应该刻上什么样的启动程序？换句话说，这个现代计算机具备的最基本功能是什么？

从高效的角度（不代表真实的历史，真实历史可能经历过很多曲折的发展过程），我想，它最好具备下面的这些能力。

- 键盘和显示器的驱动程序。
- 当时最主流的外置存储设备（不一定是现代的硬盘）的驱动程序。
- 一个汇编程序编辑器。可从存储中读取汇编程序代码，修改并保存到存储中。
- 一个汇编编译器。可将汇编程序代码编译成机器代码程序，并保存到存储中。
- 可以执行一段保存在外置存储设备中的机器代码程序。

本质上，我们是要实现一个最小化的计算能力可自我迭代的计算机。

这个时期还没有操作系统（当然，把ROM上的启动程序BIOS看做一种最小化的操作系统，我觉得也可以，但毕竟不是现实中我们说的操作系统）。

汇编语言的出现要早于操作系统。操作系统的核心目标是软件治理，只有在计算机需要管理很多的任务时，才需要有操作系统。

所以，在没有操作系统之前，BIOS 包含的内容很可能是下面这样的：

- 外置存储设备的驱动程序；
- 基础外部设备的驱动程序，比如键盘、显示器；
- 汇编语言的编辑器、编译器；
- 把程序的源代码写入磁盘，从磁盘读入的能力。

最早期的计算机毫无疑问是单任务的，计算的职能也多于存储的职能。每次做完任务，计算机的状态重新归零（回到初始状态）都没有关系。

但是，有了上面这样一个 BIOS 程序后，计算机就开始发展起它存储的能力：程序的源代码可以进行迭代演进了。

这一步非常非常重要。计算机的存储能力的重要性如同人类发明了纸。纸让人类存储了知识，一代代传递下去并不断演进，不断发扬光大。

而同样有了存储能力的计算机，我们的软件程序就会不断被传承，不断演进发扬光大，并最终演进出今天越来越多姿多彩的信息科技的世界。

## 结语

今天我们一起回到了编程的史前时代，共同回溯了编程语言诞生的历史。

为了不再用“0101”表达自己的思想，人们创造了汇编语言，这一步让编程成为一个纯软件行为，程序员这一个分工也由此诞生。

为了进一步支持程序员这个职业，我们设计了MVP版（最小化可行产品）的可自我迭代的计算机。有了这个计算机，我们就可以不断演进，并最终演进出今天越来越多姿多彩的信息科技的世界。

## 架构上的思考题

在上一讲中，我们谈架构思维时提到，我们在需求分析时，要区分需求的变化点和稳定点。稳定点往往是系统的核心能力，而变化点则需要对应地去考虑扩展性上的设计。

今天，我们假设要实现一个最小化的计算能力可自我迭代的计算机，需求如上所述。

那么，它的变化点和稳定点分别是什么？为此，你会怎么设计出哪些子系统，每个子系统的规格是什么？扩展性上有哪些考虑？

欢迎把你的想法告诉我，我们一起讨论。感谢你的收听，再见。
