You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
13 KiB
Markdown

2 years ago
# 05 | 思考题解读:如何实现可自我迭代的计算机?
你好,我是七牛云许式伟。
在“[03 | 汇编:编程语言的诞生](https://time.geekbang.org/column/article/91425)”中,我给出了一个架构思考题:
**第一台以键盘+显示器为标准输入输出的现代计算机出现后,一个最小功能集,但计算能力可自我迭代的计算机应该是什么样的?**
从需求上来说,我们期望它有如下能力。
* 键盘和显示器的驱动程序。
* 当时最主流的外置存储设备(不一定是现代的硬盘)的驱动程序。
* 一个汇编程序编辑器。可从存储中读取汇编程序代码,修改并保存到存储中。
* 一个汇编编译器。可将汇编程序代码编译成机器代码程序,并保存到存储中。
* 支持执行一段保存在外置存储设备中的机器代码程序。
那么,它的变化点和稳定点分别是什么?为此,你会怎么设计,设计出哪些子系统,每个子系统的规格是什么?扩展性上有哪些考虑?
## 需求分析
我们前面谈架构思维时提到:**做架构,第一件事情要学会做需求分析**。
需求分析的重要性怎么形容都不过分。准确的需求分析是做出良好架构设计的基础。我个人认为,架构师在整个架构的过程中,至少应该花费三分之一的精力在需求分析上。
这也是为什么很多非常优秀的架构师换到一个新领域后,一上来并不能保证一定能够设计出良好的架构,而是往往需要经过几次迭代才趋于稳定,原因就在于:领域的需求理解是需要一个过程的,对客户需求的理解不可能一蹴而就。
所以,一个优秀的架构师除了需要“在心里对需求反复推敲”的严谨态度外,对客户反馈的尊重之心也至关重要。只有心里装着客户,才能理解好需求,做好架构。
前面我们也强调过:在需求分析时,要区分需求的变化点和稳定点。稳定点往往是系统的核心能力,而变化点则需要对应地去考虑扩展性上的设计。
那么今天我们来实战一番,要实现一个最小化的计算能力可自我迭代的计算机,我们怎么做需求分析。
## 怎么实现可自我迭代的计算机?
通过前面对计算机工作原理的分析,我们已经知道,计算机分为三大类的零部件:
* 中央处理器;
* 存储;
* 输入输出设备。
中央处理器作为“计算”能力的核心,我们已经对它的工作范畴解剖清晰,这里不提。
存储,一方面作为“计算”的输入输出,另一方面作为“计算”本身的承载(也就是程序),主要的变数在后者。存储上的程序主要是:
* 计算机主板ROM上的启动程序BIOS
* 外置存储上的软件。
接下来我们要考虑清楚的是BIOS 负责做什么,外置存储上的软件负责做什么。这里我们先不展开。
输入输出设备,除了键盘和显示器外,还有外置存储。键盘和显示器我们只需要准备好对应的驱动程序,并没有特别需要考虑的内容。主要的变数在外置存储上。
外置存储在我们为它准备好了驱动程序后,就可以对它进行数据的读写了,但是我们接着需要考虑的问题是:我们准备把外置存储的数据格式设计成什么样?
回答这个问题前,先回顾下我们要做什么。目前我们已知的功能需求有如下这些。
* 键盘和显示器的驱动程序。
* 外置存储设备的驱动程序。
* 汇编程序编辑器。可从外置存储中读取汇编程序代码,修改并保存到外置存储中。
* 汇编编译器。可将汇编程序代码编译成机器代码程序,并保存到外置存储中。
* 支持执行一段保存在外置存储设备中的机器代码程序。
我们可以看到,外置存储需要保存的内容有:
* 汇编程序的源代码;
* 汇编编译器编译出来的可执行程序。
可见,外置存储它不应该只能保存一个文件,而是应该是多个。既然是多个,就需要组织这些文件。那么,怎么组织呢?
今天我们当然知道操作系统的设计者们设计了文件系统这样的东西来组织这些文件。虽然文件系统的种类有很多比如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 指令集的迭代,以及汇编语言进化的迭代。
最终,我们设计出来的“可自我迭代的计算机”,它的系统架构看起来是这样的:
![](https://static001.geekbang.org/resource/image/95/47/95183755588918ff21a76b747a96b247.png)
你的需求分析和系统设计跟上面的架构一致吗?
不一致非常正常,架构并无标准答案。但通过对比别人的方案与自己的不同之处,可以加深你对架构设计在决策上的体会。
另外,在 “可自我迭代的计算机” 这样相对模糊需求的细化过程中,也会很自然出现不太一样的理解,这些都是正常的,这也是需求分析的重要性所在,它本身就是一个需求从模糊到细化并最终清晰定义的过程。
如果你觉得系统过于复杂,不知道如何下手,也不要紧,设计“一个可自我迭代的计算机” 的确是一个复杂的系统,它并不是一个非常适合架构新手的任务。但是我仍然希望通过这样一个例子的剖析,你对需求分析中稳定点和变化点的判断有所感悟。
如果你有什么样的想法和疑问,欢迎你给我留言,我们一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。