第二章:架构之争——冯·诺依曼的胜利
硬件前提:到1940年代末,电子管计算机已经证明了“电子计算”是可行的。但工程师们很快发现:计算机的速度瓶颈不在电子管本身,而在“如何把程序喂给计算机”。ENIAC的“接线编程”方式——改一次程序需要几天甚至几周——已经成为最大的痛点。与此同时,第二代硬件(晶体管)还在襁褓之中,但第一代电子管的经验已经足够让人们开始思考一个更深层的问题:计算机应该长成什么样子? 这个问题的答案,决定了后来所有软件的存在方式。
上一章我们讲到,ENIAC虽然证明了电子计算机是可行的,但有一个致命问题:编程靠插线。你想让它算个新东西?请先去面板上重新接线,拨弄几百个开关。改一次程序需要几周时间。
这显然不是长久之计。
于是,1945年前后,一群数学家、工程师和物理学家开始思考一个根本性的问题:计算机的“大脑”到底该怎么设计? 程序应该放在哪里?数据应该放在哪里?它们之间是什么关系?CPU应该怎么知道下一步做什么?
这些问题听起来很抽象,但它们有一个具体的答案——一个后来被写进所有计算机教科书的名字:冯·诺依曼架构。
但冯·诺依曼的答案不是唯一的答案。在他提出自己的设计方案的同时,还有另一种架构——哈佛架构——也在实验室里诞生。两种架构有一场无声的较量,而胜负的结果,直到今天还在影响你手里的每一台设备。
这一章,我们来拆解这场“架构之争”。我们会回答三个问题:
- 冯·诺依曼架构的核心思想是什么?
- 哈佛架构凭什么挑战它?
- 为什么冯·诺依曼赢了?以及这个“赢”带来了什么后果?
一、冯·诺依曼这个人
在讲架构之前,先花点时间认识一下提出这个架构的人。因为约翰·冯·诺依曼(John von Neumann)实在太传奇了,不介绍一下他,你可能会低估这个架构的分量。
冯·诺依曼是20世纪罕见的全才。他出生于匈牙利布达佩斯的一个犹太家庭,从小就是神童——6岁就能心算八位数除法,8岁精通微积分,23岁获得数学博士学位。他的研究横跨数学、物理、经济学、计算机科学、量子力学、博弈论……在每一个领域都做出了奠基性的贡献。
他有这样一个名声:“冯·诺依曼什么都比你在行。” 你跟他讲数学,他比你懂得多;你跟他讲物理,他还是比你懂得多;你跟他讲历史、讲法律、讲音乐,他发现他在这些方面也比你在行。和他合作过的人说:每次和他一起吃饭,饭后大家都会多喝几杯,好让自己觉得“其实我也没那么差”。
1940年代,冯·诺依曼在洛斯阿拉莫斯实验室参与原子弹的研制,需要大量的数值计算。当时只有两种计算工具:手摇计算器和正在开发的ENIAC。冯·诺依曼天天泡在ENIAC项目组里,亲眼看到了“接线编程”的痛苦。他开始琢磨:能不能把程序也存进计算机的内存里,让机器自己读取指令,而不是靠人工接线?
1945年6月30日,冯·诺依曼写了一份只有101页的报告,标题叫《First Draft of a Report on the EDVAC》(关于EDVAC的报告初稿)。EDVAC是ENIAC的下一代机器。这份报告后来被公认为现代计算机设计的“圣经”。在报告中,他提出了一种全新的计算机组织方式——后来被称为冯·诺依曼架构。
有意思的是:这份报告上只有冯·诺依曼一个人的名字,但里面的思想并不全是他一个人的。ENIAC的两位主要设计者——莫奇利和埃克特——在报告写作过程中也贡献了许多想法。但冯·诺依曼是那个把零散的想法系统化、抽象成一个通用模型的人。他的贡献不在于“发明”——而在于把一堆复杂的工程问题提炼成了一个清晰的数学模型。这种能力,正是他作为数学家的看家本领。
二、冯·诺依曼架构的核心:存储程序
冯·诺依曼架构的核心思想,用一句话就可以概括:把程序和数据放在同一个存储器里,让CPU自动从内存里逐条读取指令并执行。
听起来平平无奇?但在1945年,这是一个革命性的想法。
在ENIAC上,程序是靠硬接线的——你要改变程序,就要重新连接电子管之间的导线。程序和数据是分离的,而且程序是“固化”在硬件里的。冯·诺依曼说:不,我们应该把程序也看作“数据”——它也是一种可以存、可以改、可以被计算机自己读取的数字信息。
于是,冯·诺依曼架构给出了一个标准化的部件模型。任何遵循这个模型的计算机,都必须包含五大部件:
- 存储器(Memory):用来存储程序和数据。程序和数据混在一起放在同一个空间里——这后来既是优点也是缺点。
- 运算器(ALU,Arithmetic Logic Unit):负责执行算术运算(加减乘除)和逻辑运算(与或非)。
- 控制器(Control Unit):负责从存储器里读取指令,解码后告诉运算器该干什么。
- 输入设备(Input):把程序和数据送进计算机。
- 输出设备(Output):把计算结果送出来。
一个经典的“冯·诺依曼循环”是这样运行的:取指→解码→执行→取指→解码→执行…… 控制器从内存里取出第一条指令,解读它,然后让运算器去执行;执行完后,控制器自动取出下一条指令,如此反复。这个“自动逐条执行”的过程,就是“存储程序”的核心魅力——你不需要人工干预,CPU会自动跑完整个程序。
到今天,你在用的每一台电脑、手机、平板——只要它是一台通用计算机——内部都是按照这个模型在运转。你写的代码被编译成机器指令,存在内存里;CPU不断地从内存里取指令、执行、取下一条。冯·诺依曼架构,就是数字世界的“牛顿力学”。
三、哈佛架构:一个更快的挑战者
冯·诺依曼架构不是唯一的答案。几乎在同一时期,另一种架构也在悄然萌芽——哈佛架构。
哈佛架构的名字来自哈佛大学的马克一号计算机(就是我们在第一章提到的那台继电器计算机)。在马克一号的设计中,程序和数据是分开存储的——程序放在打孔纸带上,数据放在计数器里。这个物理分离的设计,后来被提炼成一种独立的架构思想。
哈佛架构的核心区别在于:指令存储器和数据存储器是物理分离的,各有各的总线。
为什么要分开?因为这样可以并行。在冯·诺依曼架构中,指令和数据挤在同一条总线上——你想取指令的时候不能取数据,想取数据的时候不能取指令。这就是所谓的“冯·诺依曼瓶颈”(Von Neumann Bottleneck)。CPU工作再快,也要等内存总线空出来。
哈佛架构用分离总线的办法绕开了这个瓶颈。你可以在取指令的同时,也去取数据。这就是“指令并行”的早期形态。后来几乎所有的高性能CPU——从你的电脑到你的手机——都在内部采用了某种形式的哈佛架构:虽然你的电脑从外部看是冯·诺依曼(程序和数据都在同一块内存条里),但在CPU内部的一级缓存,指令缓存和数据缓存是分开的。这是为了让CPU跑得更快。
那为什么我们不说“哈佛架构赢了”?
因为哈佛架构在整体上有一个代价:灵活性下降。在哈佛架构下,程序和数据是物理隔离的。如果想在运行时修改程序(比如运行时代码生成、JIT编译、动态加载DLL等),就很困难。此外,哈佛架构要求指令存储器和数据存储器的容量分开规划——程序空间不够要整体换芯片,数据空间浪费了也不能挪用给程序。
冯·诺依曼架构虽然慢一点(同一条总线,取指令和取数据要分时),但它简单、灵活、够用。一条总线搞定所有事情,硬件设计简单,成本低,而且“程序即数据”的理念带来了巨大的编程便利——你可以写一个程序来生成另一个程序,你可以把代码当做数据来分析和修改。
所以,最终的市场选择是:外存统一(冯·诺依曼架构成为面向开发者的通用模型),但内部加载了哈佛架构的优化(缓存分离)。 这样一种混合形态,实际上是“妥协”的结果。而妥协的原因,恰恰在于工业界对成本、灵活性、生态兼容性的综合考量。
四、为什么冯·诺依曼架构成了主流?
回到1950年代,当计算机还是一个新兴行业的时候,冯·诺依曼架构和哈佛架构各有支持者。但最终,冯·诺依曼架构成为了“通用计算机”的标准模板,而哈佛架构主要出现在嵌入式系统、DSP(数字信号处理器)和缓存内部等特定场景。
原因可以归结为三点:
1. 简单就是美
冯·诺依曼架构只需要一套内存系统、一套总线。硬件设计简单,生产门槛低。对于1940-1960年代的计算机厂商来说,这个“低成本”的优势是致命的。买计算机的人主要是政府和军方客户,他们要的不是极致性能,而是“能跑起来、别太贵”。哈佛架构带来的性能提升在当时并不明显,但双套存储系统和双套总线的成本却是实打实的。
2. 程序即数据:灵活性的巨大威力
冯·诺依曼架构把程序和数据混在一起,在早期看起来是个“安全隐患”——程序可能被意外覆盖。但后来人们发现:“程序即数据”正是现代计算机灵活性的根源。
比如,你写了一个C++程序,编译器把它编译成机器码,这段机器码是数据(存在硬盘上),但它也是程序(可以被CPU执行)。你可以写另一个程序(比如链接器、加载器)来读取、修改、重新排列这段机器码。你可以写一个JIT编译器,在运行时生成新的代码并立即执行。所有这些魔法,都建立在一个前提上:数据和指令是同一个东西,可以互相转换。
哈佛架构的物理分离,虽然在速度上有优势,但在灵活性上做出了牺牲。对于通用计算机——一台要用来运行Word、浏览器、游戏、数据库的机器——灵活性比那点速度提升更重要。
3. 生态锁定和开发者惯性
到1960年代末,几乎所有高级编程语言——FORTRAN、COBOL、C——都是基于冯·诺依曼模型设计的。编译器生成的是按顺序取指令、执行指令的代码。如果你換成纯哈佛架构,这些现有的软件和开发工具都要重写。这种“锁定效应”是很强的:工程师习惯了冯·诺依曼的思维方式,教育体系教的是冯·诺依曼模型,现有的代码库依赖冯·诺依曼的特性。改变架构的成本,远远高过那点性能提升。
五、架构选择对后来的影响
冯·诺依曼架构的选择,像一次蝴蝶效应——它在1950年代做出的这个“简单够用”的决策,决定了几十年后整个计算产业的走向。
1. 操作系统的发展
在存储程序的概念下,操作系统本身也是一个程序——一个常驻内存、管理其他程序的程序。因为“程序即数据”,操作系统可以动态加载和卸载应用程序,可以调度进程,可以分配内存。如果程序和数据是分离的(纯哈佛架构),操作系统的这些功能会变得极其复杂——因为你要同时管理两个分离的存储空间。
2. 软件产业的诞生
冯·诺依曼架构让软件变得可以复制、可以销售、可以盗版——因为程序就是一段数据,可以被拷贝到任何一台遵循同样架构的计算机上。这直接催生了软件产业。如果每台计算机都有自己独特的硬件、独特的指令存储介质,那么为A机写的程序就不能跑到B机上,软件市场就不可能形成。
我们今天看到的所有软件公司——微软、Adobe、Oracle——都建立在这个基础之上。
3. 安全性问题的根源
冯·诺依曼架构也有它的代价。因为数据和指令在同一个空间里,恶意数据可以伪装成指令让CPU执行——这就是缓冲区溢出攻击的原理。你输入一个超长的字符串,可能会覆盖掉内存里的返回地址,让CPU跳转到攻击者注入的“数据”上去执行。
整个计算机安全领域的一半问题——堆栈溢出、代码注入、DEP(数据执行保护)、ASLR(地址空间布局随机化)——都源于数据和指令没有物理分离。从这个角度看,哈佛架构其实更安全。但工业界选择了在冯·诺依曼架构的基础上打补丁,而不是切换到另一种架构。这又是“路径依赖”的一个典型案例。
六、小结:被“妥协”掉的哈佛架构
哈佛架构没有消失。它活在了每一个高性能CPU的缓存里——L1指令缓存和L1数据缓存就是哈佛教科书式的分离。但作为一个“纯粹的通用计算机架构”,它输给了冯·诺依曼。
这个输赢不是“谁的技术更高”,而是“谁的成本更低、谁更灵活、谁赶上了开发者生态的顺风车”。
今天我们回过头看,会发现在多次技术路线选择中,“足够好”常常打败“理论最优”。冯·诺依曼架构不是最快的,不是最安全的,甚至不是最优雅的——但它简单、够用、便宜,并且正好赶上了计算机从实验室走向市场的历史窗口。一旦成千上万的程序员学会了在这个模型上编程,再想改变它,就几乎不可能了。
这也是我们整个系列的一个核心主题:历史不是被“最优解”推动的,而是被“在当时条件下看起来最合理”的选择推动的。
七、留下一个悬念:架构定了,软件呢?
冯·诺依曼架构给计算机画好了“骨架”——五大部件,存储程序,逐条执行。但有了骨架,还缺一个“管家”。
在冯·诺依曼架构下,程序可以直接操作硬件——你可以直接往内存的某个地址写入数据,可以直接控制I/O端口。这在一台只有一个程序、一个人用的机器上没问题。但当计算机要同时给很多人用、同时跑很多程序时,就会出现混乱:程序A把程序B的内存覆盖了怎么办?两个程序同时抢打印机怎么办?
我们需要一个“管家”——一个常驻内存的程序,来管理所有硬件资源,调度所有应用程序,防止它们互相捣乱。
这个“管家”就是操作系统。
下一章,我们将讲述操作系统的诞生——从批处理系统到分时系统,从CTSS到Multics,以及为什么那个“雄心勃勃”的Multics项目最终失败了,却催生了UNIX。
下一章预告: 《操作系统的诞生:从裸机到管家》。我们将回答:为什么需要操作系统?批处理系统是怎么让人排队等机器的?分时系统是怎么让“多人共用一台电脑”成为可能的?以及——那个叫Multics的项目,是怎么“死”的?