<audio id="audio" title="05 | 思考题解读：如何实现可自我迭代的计算机？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d5/5e/d5562e89cfe70a9bf535daa6c9df705e.mp3"></audio>

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

在“[03 | 汇编：编程语言的诞生](https://time.geekbang.org/column/article/91425)”中，我给出了一个架构思考题：

**第一台以键盘+显示器为标准输入输出的现代计算机出现后，一个最小功能集，但计算能力可自我迭代的计算机应该是什么样的？**

从需求上来说，我们期望它有如下能力。

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

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

## 需求分析

我们前面谈架构思维时提到：**做架构，第一件事情要学会做需求分析**。

需求分析的重要性怎么形容都不过分。准确的需求分析是做出良好架构设计的基础。我个人认为，架构师在整个架构的过程中，至少应该花费三分之一的精力在需求分析上。

这也是为什么很多非常优秀的架构师换到一个新领域后，一上来并不能保证一定能够设计出良好的架构，而是往往需要经过几次迭代才趋于稳定，原因就在于：领域的需求理解是需要一个过程的，对客户需求的理解不可能一蹴而就。

所以，一个优秀的架构师除了需要“在心里对需求反复推敲”的严谨态度外，对客户反馈的尊重之心也至关重要。只有心里装着客户，才能理解好需求，做好架构。

前面我们也强调过：在需求分析时，要区分需求的变化点和稳定点。稳定点往往是系统的核心能力，而变化点则需要对应地去考虑扩展性上的设计。

那么今天我们来实战一番，要实现一个最小化的计算能力可自我迭代的计算机，我们怎么做需求分析。

## 怎么实现可自我迭代的计算机？

通过前面对计算机工作原理的分析，我们已经知道，计算机分为三大类的零部件：

- 中央处理器；
- 存储；
- 输入输出设备。

中央处理器作为“计算”能力的核心，我们已经对它的工作范畴解剖清晰，这里不提。

存储，一方面作为“计算”的输入输出，另一方面作为“计算”本身的承载（也就是程序），主要的变数在后者。存储上的程序主要是：

- 计算机主板ROM上的启动程序（BIOS）；
- 外置存储上的软件。

接下来我们要考虑清楚的是：BIOS 负责做什么，外置存储上的软件负责做什么。这里我们先不展开。

输入输出设备，除了键盘和显示器外，还有外置存储。键盘和显示器我们只需要准备好对应的驱动程序，并没有特别需要考虑的内容。主要的变数在外置存储上。

外置存储在我们为它准备好了驱动程序后，就可以对它进行数据的读写了，但是我们接着需要考虑的问题是：我们准备把外置存储的数据格式设计成什么样？

回答这个问题前，先回顾下我们要做什么。目前我们已知的功能需求有如下这些。

- 键盘和显示器的驱动程序。
- 外置存储设备的驱动程序。
- 汇编程序编辑器。可从外置存储中读取汇编程序代码，修改并保存到外置存储中。
- 汇编编译器。可将汇编程序代码编译成机器代码程序，并保存到外置存储中。
- 支持执行一段保存在外置存储设备中的机器代码程序。

我们可以看到，外置存储需要保存的内容有：

<li>
汇编程序的源代码；
</li>
<li>
汇编编译器编译出来的可执行程序。
</li>

可见，外置存储它不应该只能保存一个文件，而是应该是多个。既然是多个，就需要组织这些文件。那么，怎么组织呢？

今天我们当然知道，操作系统的设计者们设计了文件系统这样的东西，来组织这些文件。虽然文件系统的种类有很多（比如：FAT32、NTFS、EXT3、EXT4 等等），但是它们有统一的抽象：文件系统是一颗树；节点要么是目录，要么是文件；文件必然是叶节点；根节点是目录，目录可以有子节点。

但是，文件系统（File System）是否是唯一的可能性？当然不是。键值存储（Key-Value 存储）也挺好，尤其是早期外置存储容量很可能极其有限的情况下。可以做这样统一的抽象：

- 每个文件都有一个名字（Key），通过名字（Key）可以唯一定位该文件，以进行文件内容的读写；
- 为了方便管理文件，可以对文件名做模糊查询（List），查询（List）操作支持通配符（比如我们现在习惯用的`*`和`?`）；
- 未来外置存储的空间有可能很大，需要考虑文件管理的延展性问题；可以考虑允许每个文件设定额外的元数据（Meta），例如创建时间、编辑时间、最后访问时间、以及其他用户自定义的元数据。通过元数据我们也可以检索（Search）到我们感兴趣的文件。

聊完了外置存储，让我们再回来看看 BIOS 和外置存储的软件怎么分工。

首先，BIOS 和外置存储上的软件分工的标准是什么？BIOS 是刻在计算机主板ROM上的启动程序，它的变更非常麻烦。所以 BIOS 负责的事情最好越少越好，只做最稳定不变的事情。

我们一一来看当前已知的需求。

**首先是外部设备的驱动程序**：键盘和显示器的驱动程序、外置存储设备的驱动程序。一方面，只要键盘、显示器、外置存储没有大的演进，驱动程序就不变，所以这块是稳定的；另一方面，它们是 BIOS 干其他业务的基础。所以，这个事情 BIOS 必然会做。

**其次是汇编程序编辑器。**编辑器的需求是模糊的，虽然我们知道它支持用户来编写程序，但是整个编辑器的操作范式是什么样的，没有规定。所以它不像是给键盘写一个驱动程序那样，是一个确定性的需求，而有很多额外的交互细节，需要去进一步明确。

你可以留意下自己日常使用的编辑器，去试着列一下它们的功能列表。你会发现小小的编辑器，功能远比你接触的大部分常规软件要多得多。

**再次是汇编编译器。**汇编编译器从输入输出来看，似乎需求相对确定。输入的是汇编源代码，输出的是可执行程序。但认真分析你会发现，它实际上也有很大的不确定性。

其一，CPU 会增加指令，这时候汇编指令也会相应地增加。对于大部分应用程序，CPU 新增的指令如果自己用不到，可以当它不存在。但是汇编语言及编译器需要完整呈现 CPU 的能力，因此需要及时跟进。

其二，虽然汇编指令基本上和机器指令一一对应，但是它毕竟是面向程序员的生产力工具，所以汇编语言还是会演进出一些高阶的语法，比如宏汇编指令。

所谓宏汇编指令，就是用一个命令去取代一小段汇编指令序列，它和C语言里面的宏非常类似。所以汇编语言并不是稳定的东西，它和其他高级语言类似，也会迭代变化。这就意味着汇编编译器也需要相应地迭代变化。

**最后，执行一段保存在外置存储设备中的机器代码程序。**这个需求看似比较明确，但是实际上需求也需要进一步细化。它究竟是基于外置存储的物理地址来执行程序，还是基于文件系统中的文件（文件内容逻辑上连续，但是物理上很可能不连续）来执行程序？

实现上，这两者有很大的不同。前者只需要依赖外置存储的驱动程序就可以完成，后者则还需要额外理解文件系统的格式才能做到。

那么，BIOS 到底怎么把执行控制权交到外置存储呢？

在学冯·诺依曼结构的时候，我们提到过，CPU 加电启动时，它会从存储的一个固定地址开始执行指令，这个固定地址指向的正是 BIOS 程序。

类似的，我们的 BIOS 也可以认定一个外置存储的固定地址来加载程序并执行，而无需关心磁盘的数据格式是什么样的。这个固定地址所在的数据区域，我们可以把它叫做引导区。

引导区的存在非常重要，它实际上是 BIOS 与操作系统的边界。

对于 BIOS 来说，执行外置存储上的程序能力肯定是需要具备的，否则它没有办法把执行权交给外置存储。但是这个能力可以是非常简约的。BIOS 只需要执行引导区的程序，这个程序并不长，完全可以直接读入到内存中，然后再执行。

我们是否需要基于文件系统中的文件来执行程序的能力？答案是需要。因为汇编编译器编译后的程序在外置存储中，需要有人能够去执行它。

综上，我们确认 BIOS 需要负责的事情是：

- 键盘和显示器的驱动程序；
- 外置存储设备的驱动程序；
- 支持执行外置存储中引导区的机器代码程序；
- 跳转到外置存储的固定地址，把执行权交给该地址上的引导程序。

而汇编程序编辑器、汇编编译器 ，以及支持执行文件系统中的程序，则不应该由 BIOS 来负责。

那么，外置存储上的引导程序拿到执行权后干什么呢？

我们再来总结下当前我们遇到的需求。

- 需要有人负责支持外置存储的数据格式，提供统一的功能给其他程序使用。无论它是文件系统，还是Key-Value存储系统。
- 需要有人提供管理外置存储的基础能力，比如查询（List）一下外置存储里面都有些什么文件。它可以实现为一个独立的程序，比如我们命名为 ls。
- 需要有人执行外置存储上的可执行程序。它可以实现为一个独立的程序，比如我们命名为 sh。
- 汇编程序编辑器。其实这个程序和汇编语言没什么关系，就是一个纯正的文本编辑器。我们可以把这个程序命名为 vi。
- 汇编编译器。它可以实现为一个独立的程序，比如我们命名为 asm。

引导程序拿到执行权后，我们不管它额外做了哪些事情，最终它要把执行权交给 sh 程序。因为，sh 程序算得上是可自我迭代的计算机扩展性的体现：通过 sh 程序来执行外置存储上的任意程序，这也相当于在扩展 CPU 的指令集。

## 结语

我们来回顾一下今天的内容。一个最小功能集、计算能力可自我迭代的计算机，它的变化点和稳定点分别是什么？为此，你会怎么设计，设计出哪些子系统，每个子系统的规格是什么？扩展性上有哪些考虑？

需求的变化点在于下面这几点。

- 外置存储的数据格式。对此，我们设计文件系统（或Key-Value存储）子系统来负责这件事情。另外，我们也提供了 ls 程序来管理外置存储中的文件。
- 用户最终拿到这个计算机后，会迭代出什么能力。对此，我们设计了 sh 程序，让它支持在外置存储上执行任何应用程序。
- 编辑器的交互范式。对此，我们设计了 vi 程序，让它迭代编辑器的能力。
- 汇编语言的使用范式。对此，我们设计了 asm 程序，让它响应 CPU 指令集的迭代，以及汇编语言进化的迭代。

最终，我们设计出来的“可自我迭代的计算机”，它的系统架构看起来是这样的：

<img src="https://static001.geekbang.org/resource/image/95/47/95183755588918ff21a76b747a96b247.png" alt="">

你的需求分析和系统设计跟上面的架构一致吗？

不一致非常正常，架构并无标准答案。但通过对比别人的方案与自己的不同之处，可以加深你对架构设计在决策上的体会。

另外，在 “可自我迭代的计算机” 这样相对模糊需求的细化过程中，也会很自然出现不太一样的理解，这些都是正常的，这也是需求分析的重要性所在，它本身就是一个需求从模糊到细化并最终清晰定义的过程。

如果你觉得系统过于复杂，不知道如何下手，也不要紧，设计“一个可自我迭代的计算机” 的确是一个复杂的系统，它并不是一个非常适合架构新手的任务。但是我仍然希望通过这样一个例子的剖析，你对需求分析中稳定点和变化点的判断有所感悟。

如果你有什么样的想法和疑问，欢迎你给我留言，我们一起讨论。如果你觉得有所收获，也欢迎把文章分享给你的朋友。感谢你的收听，我们下期再见。
