<audio id="audio" title="02 | 大厦基石：无生有，有生万物" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/eb/7e/ebf1e26f5139f87199ae7879c80c267e.mp3"></audio>

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

在上一讲中，我们把“构建一个应用程序”类比成“构建一座大厦”，并从宏观全局的视角剖析了应用程序这座大厦的构成。今天，我们将更加近距离地去解剖这座大厦的地基：冯·诺依曼体系结构。

## 解剖架构的关键点是什么？

在解剖之前，我想和你先谈谈“解剖学”：**我们应该如何去分析架构设计中涉及的每一个零部件。换一句话说，当我们设计或分析一个零部件时，我们会关心哪些问题。**

**第一个问题，是需求。**这个零部件的作用是什么？它能被用来做哪些事情？（某种意义上来说更重要的是）它不会被用来做哪些事情？

你可能会说，呀，这个问题很简单，既然我设计了这个零部件，自然知道它是用来干嘛的。但实质上这里真正艰难的是“为什么”：为何这个零件被设计成用来干这些事情的，而不是多干一点事情，或者为什么不是少干某些事情？

**第二个问题，是规格。**这个零部件接口是什么样的？它如何与其他零件连接在一起的？

规格是零部件的连接需求的抽象。符合规格的零部件可以有非常多种可能的实现方案，但是，一旦规格中某个条件不能满足了，它就无法正常完成与其他零件的连接，以达到预期的需求目标。

规格的约束条件会非常多样化，可能是外观（比如形状和颜色），可能是交互方式（比如用键盘、鼠标，或者语音和触摸屏），也可能是质量（比如硬度、耐热性等等）。

那么，冯·诺依曼体系结构的需求和规格又是什么样的呢？

## 为“解决一切的问题”而生

冯·诺依曼体系结构不但是应用程序这座大厦的地基，同时也是整个信息科技的地基。

**当我们去审视整个信息科技时，仅把它形容为一座大厦显得如此不贴切，甚至你也不能用“一个城市”去形容它，事实上，它更像是一个无中生有的全新世界：在其中，有个体、有族群、有生态，还有喜怒哀乐。**

冯·诺依曼体系结构的迷人之处在于，从需求来说，它想解决一切问题。解决一切可以用“计算”来解决的问题。

“计算”的边界在哪里？今天我们还没有人能够真正说得清。计算能不能解决“智能”的问题？通过计算能力，计算机是否终有一天可以获得和人类一样的智能？

今天人工智能热潮的兴起，证明对于这个问题我们很乐观：计算终将解决智能的问题。尽管我们不能确定什么时候能够达到，但是让人欣慰的是，我们一直在进步 —— 如果人类智能无法完成进一步的进化，那么我们就一直一直在前进，最终无限逼近甚至超越人类智能。

甚至有科幻小说家设想（例如在Google的“AlphaGo”大热后，霍炬和西乔创作的漫画“BetaCat”），计算机演进出超过人类的智能是生物进化的一个自然演进路径，它将取代人类成为新的食物链顶端，并最终基于其悠久的生命力，去完成人类有限生命无法实现的星际航行之路。

## 冯·诺依曼体系的规格

为了实现“解决一切可以用‘计算’来解决的问题”这个目标，冯·诺依曼引入了三类基础零部件：

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

首先我们来看看存储。它负责存放计算涉及的相关数据，作为计算的输入参数和输出结果。

我们日常见到的存储设备非常的多样化。比如：中央处理器自己内置的寄存器、内存、传统机械硬盘、USB固态硬盘、光盘等等。

从中央处理器的角度，存储可简单分为两类：一类是内置支持的存储，通过常规的处理器指令可直接访问，比如寄存器、内存、计算机主板的ROM。一类是外置存储，它们属于输入输出设备。中央处理器本身并不能直接读写其中的数据。

冯·诺依曼体系中涉及的“存储”，指的是中央处理器内置支持的存储。

我们再来看看输入输出设备。它是计算机开放性的体现，大大拓展了计算机的能力。每个设备通过一个端口与中央处理器连接。通过这个端口地址，中央处理器可以和设备进行数据交换。数据交换涉及的数据格式由设备定义，中央处理器并不理解。

但这并不影响设备的接入。设备数据交换的发起方（设备使用方）通常理解并可以解释所接收的数据含义。为了方便使用，设备厂商或操作系统厂商通常会提供设备相关的驱动程序，把设备数据交换的细节隐藏起来，设备的使用方只需要调用相关的接口函数就可以操作设备。

最后我们来看看中央处理器。它负责程序（指令序列）的执行。指令序列在哪里？也存放在存储里面。计算机加电启动后，中央处理器从一个固定的存储地址开始执行。

中央处理器支持的指令大体如下（我们在第一篇文章中也曾提到过）：

- 计算类，也就是支持我们大家都熟知的各类数学运算，如加减乘除、sin/cos等等；
- I/O类，从存储读写数据，从输入输出设备读数据、写数据；
- 指令跳转类，在满足特定条件下跳转到新的当前程序执行位置、调用自定义的函数。

和“解决一切可以用‘计算’来解决的问题”这个伟大的目标相比，冯·诺依曼体系的三类零部件的规格设计显得如此精简。

为什么这么简洁的规格设计，居然可以解决这么复杂的需求？

## 需求是怎么被满足的？

我们来设想一下：假如今天让我们从零开始设计一个叫电脑的东西，我们的目标是“解决一切可以用‘计算’来解决的问题”。

对于这么含糊的需求，如果你是“电脑”这个产品的主架构师，你会如何应对？

让我们来分析一下。

一方面，需求的变化点在于，要解决的问题是五花八门包罗万象的。如何以某种稳定但可扩展的架构来支持这样的变化？而另一方面，需求的稳定之处在于，电脑的核心能力是固定的，怎么表达电脑的核心能力？

电脑的核心能力是“计算”。什么是计算？计算就是对一个数据（输入）进行变换，变为另一个数据（输出）。在数学中我们把它叫“函数”。如下：

> 
y = F(x)


这里 x、y 是数据。它们可能只是一个简单的数值，也可能是文本、图片、视频，各种我们对现实问题进行参数化建模后的测量值，当然也可能是多个输入数据。但无论它的逻辑含义为何，物理上都可以以一段连续的字节内容来表达。用 Go 的语法表达就是：

```
func F(x []byte) (y []byte)

```

那么 x、y 物理上在哪里？思路推理到这里，“存储” 这个概念自然就产生了：存储，就是存放计算所要操作的数据的所在。

下面的问题是：一个具体的计算（也就是 F 函数）怎么表达？

这里的难点在于，F 对于电脑的架构师来说是未知的。那么，怎么设计一种系统架构让用户可以表达任意复杂的计算（函数）？

逻辑上来看，无论多复杂的自定义函数，都可以通过下面这些元素的组合来定义：

- 内置函数，比如整数或小数运算（加减乘除、sin/cos等）；
- 循环和条件分支；
- 子函数（也是自定义函数）。

这样一来，对于任意的一个具体的计算（自定义函数）来说，都可以用一组指令序列来表达。

那么函数 F 物理上在哪里？以指令序列形式存放在存储里面。所以，存储不只存放计算所要操作的数据，也存放“计算”本身。

只是，存储里面存放的“计算”只是数据，需要有人理解并执行这些数据背后的计算行为，才变成真正意义的“计算”。这个执行者，就是中央处理器（CPU）。它支持很多计算指令，包括执行内置函数、循环和条件分支、执行子函数等。

所以，有了中央处理器+存储，就可以支持任意复杂的“计算”了。

<img src="https://static001.geekbang.org/resource/image/cf/37/cf77b8fbe8a559cecbb264c390bc7337.png" alt="">

只是如果电脑只有“中央处理器+存储”，那它就如同一个人只有头脑而没有四肢五官，尽管很可能很聪明，但是这种聪明无法展现出来，因为它没法和现实世界发生交互。

交互，抽象来看就是输入和输出。对人来说，输入靠的是五官：眼睛看、耳朵听、鼻子闻、舌头尝，以及肌肤接触产生的触觉。输出靠语言（说话）和各种动作，如微笑、眨眼、皱眉、手势等等。

对于电脑来说，输入输出的需求就更多了，不只是四肢五官，而可能是千肢万官。

从输入需求来说，可能采集静态图像、声音、视频；也可能采集结构化数据，如GPS位置、脉搏、心电图、温度、湿度等；还可能是用户控制指令如键盘按键、鼠标、触摸屏动作等。

从输出需求来说，可能是向屏幕输出信息；也可能是播放声音；还可能是执行某项动作，如交通灯开关、汽车马达转动、打印机打印等。

但不管是什么样交互用途的器官（设备），我们要做的只是定义好统一的数据交换协议。这个数据交换机制，和网络上两台电脑通过互联网，需要通过某种数据交换协议进行通讯，需求上没有实质性的差别。

也就是说，除了纯正的“计算”能力外，中央处理器还要有“数据交换”能力（或者叫IO能力）。最终，**电脑可以被看做由 “中央处理器+存储+一系列的输入输出设备”** 构成。如下图：

<img src="https://static001.geekbang.org/resource/image/28/a9/28ef9c0241c5c34abb85148453379fa9.png" alt="">

尽管输入输出设备引入的最初灵感可能是来自于“交互”，但是当我们去审视输入输出设备到底是什么的时候，我们很自然发现，它能够做的不单单是交互。

比如常见的外置存储如机械硬盘、光盘等，它们也是输入输出设备，但并不是用于交互，而是显著提升了电脑处理的数据体量。

输入输出设备从根本上解决的问题是什么？

是电脑无限可能的扩展能力。

最重要的一点，输入输出设备和电脑是完全异构的。输入输出设备对电脑来说就只是实现了某项能力的黑盒子。

这个黑盒子内部如何？没有规定。它可以只是一个原始的数字化的元器件，也可以是另一台冯·诺依曼架构的电脑，还可以是完全不同架构的电脑，比如GPU电脑、量子计算机。

你可以发现，引入了输入输出设备的电脑，不再只能做狭义上的“计算”（也就是数学意义上的计算），如果我们把交互能力也看做一种计算能力的话，电脑理论上能够解决的“计算”问题变得无所不包。

## 架构思维上我们学习到什么？

架构的第一步是需求分析。从需求分析角度来说，关键要抓住需求的稳定点和变化点。需求的稳定点，往往是系统的核心价值点；而需求的变化点，则往往需要相应去做开放性设计。

对于“电脑”这个产品而言，需求的稳定点是电脑的“计算”能力。需求的变化点，一是用户“计算”需求的多样性，二是用户交互方式的多样性。

电脑的“计算”能力，最终体现为中央处理器的指令集，这是需求相对稳定的部分。

用户“计算”需求的多样性，最终是通过在存储中的指令序列实现。计算机加电启动后，中央处理器并不是按自己固有的“计算”过程进行，而是从一个固定的存储地址加载指令序列执行。

通常，这个固定的存储地址指向计算机主板的ROM上的一段启动程序（BIOS）。这段启动程序通常包含以下这些内容。

- 存储设备的驱动程序，用以识别常规的外置存储设备，比如硬盘、光驱、U盘。
- 基础外部设备的驱动程序，比如键盘、鼠标、显示器（显卡）。
- 设备和启动配置的基础管理能力。
- 在外置存储上执行程序的能力（中央处理器只支持在内存上执行程序，当然它也为在外置存储执行程序提供了一些支持，比如内存页缺失的中断处理）。
- 将执行权转移到外置存储（第一次安装操作系统的时候可能是光驱甚至是网络存储，平常通常是硬盘）上的操作系统启动程序。这样，操作系统就开始干活了。

这样一来，“计算”需求的多样性只需要通过调整计算机主板上的BIOS程序，乃至外置存储中的操作系统启动程序就可以实现，而不必去修改中央处理器本身。

用户交互方式的多样性，则通过定义外部设备与中央处理器的数据交换协议实现。

当我们把所有的变化点从电脑的最核心部件中央处理器剥离后，中央处理器的需求变得极其稳定，可独立作为产品进行其核心价值的演进。

## 结语

总结一下，今天，我们近距离地去解剖了整个信息世界地基：冯·诺依曼体系结构。

冯·诺依曼体系结构的不凡之处在于，它想“解决一切可以用‘计算’来解决的问题”。

为了实现这个目标，冯·诺依曼引入了三类基础零部件：中央处理器、存储、输入输出设备。所有计算机都可以看做由 “中央处理器+存储+一系列的输入输出设备” 构成。

为了方便理解，我在尝试用 Go 语言模拟来实现冯·诺依曼架构体系的电脑：

- [https://github.com/qiniu/arch/tree/master/von](https://github.com/qiniu/arch/tree/master/von)

如果你对此感兴趣，欢迎 fork 并对其进行修改迭代。

如果你对今天的内容有什么思考与解读，欢迎给我留言，我们一起讨论。如果你觉得有所收获，也欢迎把文章分享给你的朋友。感谢你的收听，我们下期再见。
