第三章-程序员的诞生《改变世界的程序员》

第三章 程序员的诞生——冯·诺依曼体系与存储程序

核心线索:现代计算机架构的确立,从硬件到软件的范式革命

如果没有冯·诺依曼,我们今天写程序的方式将完全不同。他并不是程序员——这个世界上第一个程序员另有其人——但他定义了程序运行的"地基":一个将程序和数据放在同一存储器中的架构,让计算机从"一台计算器"变成了"一台可编程的通用机器"。本章讲述这个架构如何诞生、如何运作,以及它为何统治了计算机世界整整 80 年。


3.1 冯·诺依曼:从数学家到计算机架构师

神童的诞生

1903 年 12 月 28 日,布达佩斯一个犹太银行家家庭迎来了他们的长子——诺依曼·亚诺什(Neumann János)。这个孩子后来改名为约翰·冯·诺依曼(John von Neumann),而"冯"(von)这个贵族前缀,是他父亲马克斯于 1913 年从奥匈帝国皇帝弗朗茨·约瑟夫一世那里买来的世袭头衔。

关于冯·诺依曼的童年,流传最广的传说是:6 岁时,他能心算两个 8 位数的除法,而且是用古希腊语跟父亲开玩笑。8 岁时,他已经掌握了微积分——不是"了解概念",而是能独立求解微分方程。12 岁时,他啃完了法国数学家埃米尔·波莱尔的《函数论》。到他进入大学时,他的数学知识已经超越大多数大学教授。

但冯·诺依曼并非那种"除了数学什么都不懂"的怪才。他对历史有着惊人的记忆力——据说他能背诵《双城记》和《罗马帝国衰亡史》的大段篇章。他的第一份学术论文是用匈牙利语写的,第二份就切成了德语,然后是英语、法语——他一生发表了 150 多篇论文,跨越五种语言。

他的家庭教育也非比寻常。他的父亲马克斯不仅聘请了布达佩斯大学的教授做家教,还要求孩子们学习外语、音乐、商业管理。冯·诺依曼的弟弟迈克尔后来回忆:“父亲希望我们成为完整的人,而不仅仅是聪明的犹太人。”

布达佩斯的"火星人"

20 世纪初的布达佩斯,诞生了科学史上一个令人费解的现象:在同一时期、同一城市的一小片犹太裔知识分子圈中,涌现出了一批改变世界的科学家。他们包括:

  • 西拉德·利奥(Leó Szilárd):核链式反应的构想者,与爱因斯坦共同申请了核反应堆专利
  • 维格纳·尤金(Eugene Wigner):1963 年诺贝尔物理学奖得主,对称性原理的奠基人
  • 特勒·爱德华(Edward Teller):“氢弹之父”
  • 冯·诺依曼·约翰(John von Neumann):我们本章的主角

这群人被称为"火星人"(The Martians)——因为他们都说着带有浓重匈牙利口音的英语,思维方式异于常人,仿佛来自另一个星球。物理学家伊西多·拉比曾开玩笑说:“上帝一定在布达佩斯开了一家科学商店。”

为什么是布达佩斯?历史学家总结了几个因素:

  1. 教育体系:奥匈帝国时期的布达佩斯拥有欧洲最好的中学教育体系,“法索尔街中学”(Fasori Gimnázium)培养出了冯·诺依曼、维格纳等多人
  2. 犹太知识分子的焦虑:19 世纪末的反犹主义让犹太家庭将"卓越"视为唯一的护身符
  3. 咖啡馆文化:布达佩斯的咖啡馆不仅提供咖啡,还提供免费的国际报刊、百科全书和数学家/物理学家之间的持续讨论
  4. 时代窗口:双元帝国时期(1867-1918)的相对自由和经济繁荣,为精英教育提供了土壤

冯·诺依曼本人后来总结说:“我们是 20 世纪初布达佩斯某种文化爆发的一部分。这种爆发的原因,我至今无法完全解释。”

纯数学的光辉岁月

1921 年,18 岁的冯·诺依曼同时注册了三所大学:布达佩斯大学(数学)、柏林大学(化学工程)、苏黎世联邦理工学院(化学工程)。这不是因为他对化学有特殊兴趣——而是父亲认为"纯数学养不活自己",要求他学一门"实用的手艺"。

结果呢?冯·诺依曼一边在苏黎世拿下了化学工程学位(1925 年),一边在布达佩斯完成了数学博士论文(1926 年),期间还顺便发表了关于集合论的公理化论文——这篇论文为现代集合论的公理体系奠定了基础。

1920 年代,冯·诺依曼在纯数学领域做出了以下贡献:

  • 集合论:提出了"冯·诺依曼宇宙"(von Neumann universe),一种阶次构造的集合论模型,成为现代集合论的标准框架
  • 算子理论:在希尔伯特空间上建立了无界自伴算子的谱理论,为量子力学的数学基础奠定了严谨的根基。他 1932 年出版的《量子力学的数学基础》至今仍是该领域的经典
  • 博弈论:1928 年发表的论文《关于社交游戏的理论》开创了博弈论这一全新领域。1944 年与奥斯卡·摩根斯特恩合著的《博弈论与经济行为》更是彻底改变了经济学

1933 年,30 岁的冯·诺依曼成为普林斯顿高等研究院最年轻的终身教授——与他并列的是爱因斯坦、哥德尔和赫尔曼·外尔。据说,爱因斯坦每天下午都会找冯·诺依曼下棋,而且几乎总是输。

曼哈顿计划:当数学家遇到计算机

1943 年,冯·诺依曼被招募进入曼哈顿计划,担任洛斯阿拉莫斯实验室的数学顾问。他的任务是计算核弹爆炸的冲击波动力学——这需要在给定初始条件的情况下,求解高度非线性的流体动力学偏微分方程。

当时,"计算机"一词指的是——一群数学系女生坐在桌前,用手摇计算器进行大规模数值计算。但原子弹的设计涉及的计算量远远超出了人力的极限。冯·诺依曼对冲击波的计算需要进行数十万次浮点运算——如果全靠人工,一个计算可能需要几个月。

正是在这种极度需要计算能力的状态下,冯·诺依曼开始热切关注正在萌芽的电子计算设备。

阿伯丁火车站:一次改变历史的邂逅

1944 年夏天,冯·诺依曼在马里兰州阿伯丁试验场的火车站等车时,遇到了数学家赫尔曼·戈德斯坦(Herman Goldstine)。戈德斯坦是美军弹道研究实验室的数学官,负责监督 ENIAC 项目。

戈德斯坦后来回忆这段对话:“我在车站看到了冯·诺依曼,一个我在普林斯顿就认识的数学巨人。我向他提到了 ENIAC。他立刻变得极度专注。几分钟之内,他从一个数学哲学家变成了一个对真空管、电子脉冲和逻辑门充满狂热兴趣的工程师。他问问题的速度和深度让我震惊。”

这次"火车站邂逅"直接促成了冯·诺依曼加入 ENIAC 团队担任顾问。但冯·诺依曼很快意识到:ENIAC 虽然在计算速度上是革命性的(比机电式 Mark I 快 1000 倍),但在"编程"方式上,它仍然是一个石器时代的产物。

ENIAC 的编程方式是这样的:要改变计算任务,工程师必须手动拔插电缆、拨动 6000 多个开关,整个过程需要几天甚至几周。冯·诺依曼后来写道:“这不仅慢,而且是一种智力上的耻辱——一台每秒能做 5000 次运算的机器,却要用几天来告诉它该做什么。”

EDVAC 报告:定义了未来 70 年的 101 页手稿

1945 年 6 月,冯·诺依曼完成了一份 101 页的手稿,题为《关于 EDVAC 的报告(初稿)》(“First Draft of a Report on the EDVAC”)。这份报告表面上是对 ENIAC 后续机型 EDVAC(电子离散变量自动计算机)的设计建议,实质上却定义了现代计算机的完整逻辑架构。

这份报告的核心思想,用冯·诺依曼自己的话说,可以用一句话概括:

“程序和数据应该以同一种形式存储在同一个存储器中。”

这句话是什么意思?在 ENIAC 时代,“计算步骤”(即程序)是通过物理接线来实现的,而"计算对象"(即数据)存储在电子寄存器中。程序是硬件,数据是电子信号——两者在物理上和概念上都是分开的。

冯·诺依曼的洞见是:如果把程序指令也编码成数字,存储在同一个存储器中,那么计算机就能通过修改存储器中的数字来修改自己的程序。这意味着:

  • 程序变成了数据:一段程序可以像数据一样被存储、复制、修改、传输
  • 通用性:同一台机器可以做数值计算、文字处理、图形渲染——只要加载不同的程序
  • 自修改代码的可能:程序可以在运行中修改自身指令(现代编程中基本禁止这种做法,但理念上这是革命性的)
  • 编译器的理论基础:因为程序就是数据,所以可以写一个程序(编译器)来生成另一个程序

这份报告将计算机分解为五大组件,后来被称为冯·诺依曼体系架构

组件英文核心功能类比
运算器Arithmetic Logic Unit (ALU)执行算术和逻辑运算大脑的计算中枢
控制器Control Unit (CU)控制指令执行顺序大脑的指挥中枢
存储器Memory存储程序和数据大脑的记忆
输入设备Input将外部信息送入计算机眼睛和耳朵
输出设备Output将计算结果送出计算机嘴巴和手

这五大组件的组织方式,至今仍然是你的笔记本电脑、智能手机、云服务器的基本蓝本。

谁才是真正的作者?

这里必须澄清一个历史争议:EDVAC 报告的署名只有冯·诺依曼一人,但这 101 页手稿中的许多关键思想来自 EDVAC 团队的集体讨论,特别是:

  • 约翰·莫奇利(John Mauchly)和 约翰·埃克特(J. Presper Eckert):ENIAC 的设计者,他们最早提出了"存储程序"的模糊想法
  • 阿瑟·伯克斯(Arthur Burks):逻辑设计专家,贡献了加法器和乘法器的详细设计
  • 赫尔曼·戈德斯坦:团队组织者,将冯·诺依曼引入项目的人

为什么只有冯·诺依曼署名?根据戈德斯坦后来的解释:这份"初稿"本来只是内部讨论的非正式笔记,戈德斯坦将其打字整理后只署了冯·诺依曼的名字就分发了。莫奇利和埃克特对此极为不满——他们认为冯·诺依曼"抢走"了他们的发明。

这个争议导致了两个后果:

  1. 莫奇利和埃克特申请了 ENIAC 专利,但最终因 EDVAC 报告的"公开发表"导致其专利在 1973 年被判无效
  2. 计算机架构从此被称为"冯·诺依曼架构",而非"EDVAC 团队架构"

公平地说,莫奇利和埃克特是电子计算机的工程先驱,但冯·诺依曼提供了逻辑抽象——正是这种抽象让计算机从一台特殊的计算设备变成了一个通用的符号处理系统。正如计算机历史学家迈克尔·威廉姆斯所言:“莫奇利和埃克特建造了硬件,冯·诺依曼定义了思想。两者缺一不可。”

癌症与临终的对话

1955 年,52 岁的冯·诺依曼被诊断出骨癌——很可能是他在洛斯阿拉莫斯和比基尼环礁核试验中受到的辐射所致。他在轮椅上度过了生命的最后 18 个月。

在这期间,他写完了最后一本著作《计算机与人脑》(“The Computer and the Brain”),试图用计算机的术语来理解大脑的工作机制。这本书在他去世后才出版,成为认知科学领域的早期经典。

他的好友、海军上将刘易斯·斯特劳斯描述了临终场景:

“1957 年 2 月 8 日,约翰躺在美国陆军沃尔特·里德医院的病床上,周围堆满了未完成的数学手稿。他的大脑——这个 20 世纪最强大的思维器官之一——仍然在以惊人的速度运转,但他的身体已经完全衰竭。他的最后一位访客是海军部长,他来了之后约翰请他帮助自己背诵《浮士德》的段落来测试记忆。几个小时后,约翰·冯·诺依曼在 54 岁时去世了。”

技术深入:冯·诺依曼架构五大组件的逐项解析

让我们用现代视角重新审视这五大组件,看看它们在实际硬件中是如何实现的。

1. 运算器(Arithmetic Logic Unit, ALU)

ALU 是计算机的"计算引擎"。它能执行的操作分为两类:

算术运算

  • 加法(ADD)
  • 减法(SUB)
  • 乘法(MUL)
  • 除法(DIV)
  • 递增(INC)/ 递减(DEC)

逻辑运算

  • 与(AND)
  • 或(OR)
  • 非(NOT)
  • 异或(XOR)
  • 位移(SHIFT LEFT / SHIFT RIGHT)

现代 CPU 的 ALU 通常包含多个子单元,可以并行执行不同类型的运算。Intel Core i9 处理器有 10 个以上的执行单元,包括 4 个整数 ALU、2 个浮点运算单元(FPU)和多个向量处理单元(用于 SIMD 指令如 AVX-512)。

关键指标

  • 字长:ALU 一次能处理的二进制位数。从 Intel 4004 的 4 位到现代处理器的 64 位
  • 吞吐量:每个时钟周期能完成多少个操作(超标量架构使现代 CPU 能做到 IPC > 1)
2. 控制器(Control Unit, CU)

控制器是"指挥中心",它决定:

  • 从存储器中取哪条指令
  • 该指令需要什么操作数
  • 操作数从哪里获取
  • 运算结果存到哪里
  • 下一条指令的地址是什么

控制器的核心是一个指令译码器(Instruction Decoder)。当一条 32 位或 64 位的机器指令进入译码器时,它被分解为:

  • 操作码(opcode):6-8 位,指定执行什么操作
  • 操作数地址:寄存器编号或内存地址
  • 寻址模式:指定如何解析操作数

现代 CPU 的控制器远比冯·诺依曼原始设计复杂。以 Intel 的 Core 微架构为例:

  • 控制器将复杂的 CISC 指令(x86)解码为简单的 RISC 微操作(micro-ops)
  • 使用分支预测器(Branch Predictor)猜测条件跳转的结果
  • 使用重排缓冲区(Reorder Buffer)实现乱序执行
3. 存储器(Memory)

这是"存储程序"概念的主角。在冯·诺依曼架构中,存储器是一个线性地址空间——每个存储单元有一个唯一的数字地址,程序和数据都通过这个地址来访问。

关键特征:

  • 地址总线:CPU 通过地址总线发送存储器地址,决定了最大寻址空间(32 位地址总线 → 最大 4GB,64 位 → 理论最大 16EB)
  • 数据总线:传输实际数据,宽度通常为 64 位(8 字节)
  • 控制总线:发送读/写信号和时序信号

现代的"存储器"实际上是一个多级层次结构(详见 3.4 节),但冯·诺依曼原始概念中的"主存储器"在今天对应的是 RAM(随机访问存储器)。

4. 输入设备(Input)

将外部世界的信息转换为计算机能理解的二进制数据:

  • 1940 年代:穿孔卡片、纸带
  • 1970 年代:键盘、磁带
  • 1990 年代:鼠标、扫描仪、麦克风
  • 2010 年代:触摸屏、摄像头、传感器、LIDAR、脑机接口
5. 输出设备(Output)

将计算机内部的二进制数据转换为人能理解的形式:

  • 1940 年代:电传打字机、指示灯阵列
  • 1970 年代:点阵打印机、CRT 显示器
  • 1990 年代:彩色喷墨打印机、LCD 屏幕
  • 2010 年代:4K OLED 显示器、3D 打印机、VR 头显

一个值得注意的趋势是:输入和输出的边界正在模糊。触摸屏既是输入也是输出,VR 头显同时追踪你的手部动作(输入)并渲染虚拟环境(输出),脑机接口更是将两个方向统一在一个通道中。

番外篇:冯·诺依曼与奥本海默

冯·诺依曼和奥本海默是洛斯阿拉莫斯最引人注目的两人组合。奥本海默是实验室主任,博学、优雅、左翼、爱好梵文;冯·诺依曼是数学顾问,务实、诙谐、保守、爱好聚会和豪车。

两人的分歧在 1949-1954 年间达到顶峰。苏联 1949 年成功试爆原子弹后,美国内部展开了"是否发展氢弹"的激烈辩论。奥本海默反对氢弹研制,认为这是一种"种族灭绝武器",在道德上是不可接受的。冯·诺依曼支持氢弹研制,他的逻辑是冷冰冰的博弈论:“与一个不遵守规则的对手博弈,唯一的纳什均衡是先发制人。”

1954 年,奥本海默被剥夺安全许可,实质上被逐出了美国核决策圈。冯·诺依曼没有被要求作证反对奥本海默,但他在私下表示奥本海默的态度是"不理性的"。尽管如此,两人直到冯·诺依曼去世前都保持着一种奇特的友谊——他们会在普林斯顿高等研究院的午餐会上讨论量子力学,然后各自回到敌对的政治阵营。

多年后,冯·诺依曼临终前说了一句意味深长的话:“也许罗伯特(奥本海默)是对的,但我们永远无法知道了。”

深入细节:ENIAC 到底有多"难编程"?

为了让你真正理解冯·诺依曼的"存储程序"概念有多么革命性,我们需要直观感受一下 ENIAC 的编程方式。

ENIAC 有 40 个面板,每个面板大约 0.6 米宽、2.4 米高。面板上布满了插孔和旋转开关。要"编程"ENIAC 做一个特定计算,操作员(通常是 6 位女性数学家——包括凯·麦克纳尔蒂、贝蒂·斯奈德、玛琳·韦斯科夫、弗朗西斯·比拉斯、露丝·利希特曼和吉恩·巴蒂克,她们被称为"ENIAC 六朵金花")需要:

  1. 根据计算逻辑设计电缆连接图:哪根电缆从哪个面板的哪个输出端口连接到哪个面板的哪个输入端口
  2. 物理上拔插数百根电缆(每根电缆约 1.2 米长,两端有香蕉插头)
  3. 设置 3,000 多个旋转开关到正确的数值(十进制数字和函数表索引)
  4. 检查所有连接是否有短路或断路
  5. 运行一个测试计算来验证"程序"是否正确
  6. 如果错了,从第 1 步重新开始

整个过程通常需要2-3 天来完成一个中等复杂度的计算。而计算本身——比如一个弹道轨迹——只需要 30 秒。

换句话说,编程时间和计算时间之比是 5000:1。你花两天准备,机器花 30 秒算完。这就像你花 3 小时准备食材,然后用微波炉加热 30 秒——极度失衡。

冯·诺依曼的存储程序概念将这个比例彻底反转。在 EDVAC 上,你只需在存储器中写入新的指令序列——这可以在几分钟内完成(早期用纸带输入),而不是几天。

冯·诺依曼的私人生活:天才的另一面

冯·诺依曼的社交生活和他的智力一样丰富多彩。他喜欢举办派对,拥有一流的酒窖,开车风格极其激进——他曾在普林斯顿的一条主干道上撞毁过多辆汽车,以至于普林斯顿警察局有一个"冯·诺依曼角落"(von Neumann corner)的俚语,指代他经常出事故的那个弯道。

他的第一任妻子玛丽埃特·科维西(Mariette Kövesi)在 1937 年与他离婚,原因之一是"他太专注于数学,无法成为一个正常的丈夫"。他的第二任妻子克拉拉·丹(Klara Dán)是一位匈牙利裔计算机程序员——她后来编写了 ENIAC 转换为存储程序计算机的代码,成为世界上最早的女性程序员之一。

冯·诺依曼的同事、诺贝尔物理学奖得主汉斯·贝特(Hans Bethe)曾这样描述他:“冯·诺依曼的大脑是一个完美的仪器,以至于我一直想知道他是否真的是人类。但我很快发现,他确实是人——他喜欢美食、美酒、美女和八卦,就像任何一个正常人一样。只是他的大脑恰好比我们快两个数量级。”

金句

“一台能做计算的机器并不稀奇。一台能通过改变指令来做任何计算的机器,才是革命。冯·诺依曼给的,正是这种通用性。”


3.2 冯·诺依曼体系架构详解

五个组件,一个原则

在 3.1 节我们已经认识了五大组件。现在让我们深入理解它们如何协同工作——这是理解所有现代程序设计的基础。

首先,我们要理解冯·诺依曼架构中最重要的一个约束,它既是优点也是诅咒——冯·诺依曼瓶颈

冯·诺依曼瓶颈:计算机的七寸

想象你在一个巨大的图书馆里工作。图书馆的书架上有所有数据,你的桌子上有一支笔、一张草稿纸和一个计算器。每次你要做一个计算,你必须:

  1. 走到书架上找到第一本书
  2. 回到桌前,翻阅书找到需要的数字
  3. 在计算器上做一次加法
  4. 把结果写回书架上另一本书
  5. 重复 1-4,做几十亿次

这就是冯·诺依曼架构的本质。CPU(你的桌子 + 计算器 + 你的大脑)和内存(书架)之间只有一条路——你不可以同时走两条路。每次 CPU 需要新的数据或指令,它都必须停下来等待数据从内存传过来。

更糟糕的是:CPU 的工作速度极快(现代 CPU 一个时钟周期约 0.2 纳秒,即每秒 50 亿次),而内存的响应速度相对极慢(DDR5 内存的延迟约 15 纳秒,是 CPU 周期的 75 倍)。

这就是冯·诺依曼瓶颈CPU 处理数据的速度远大于它在主存之间传输数据的速度。

一个更形象的比喻:冯·诺依曼瓶颈就像用一根吸管来喝一个游泳池的水。 你的嘴巴(CPU)吸得很快,但吸管的直径(总线带宽)限制了整体速度。

具体数据对比(2025 年的典型值):

指标CPU 速度内存速度比例
延迟~0.2 ns(单周期)~15 ns(CAS延迟)75x
带宽~100 GB/s(L1缓存)~50 GB/s(DDR5-6400 双通道)2x
带宽~50 GB/s(L1缓存)~200 MB/s(NVMe SSD)250x

为了解决这个瓶颈,工程师们发明了缓存层次结构(Cache Hierarchy),但这是 3.4 节的内容了。

指令周期:计算机的心跳

每一台冯·诺依曼架构的计算机——从 1976 年的 Apple I 到 2025 年的 MacBook Pro——都在日复一日地执行同一套基本循环。这套循环被称为指令周期(Instruction Cycle),它由四个阶段组成:

FETCH → DECODE → EXECUTE → WRITE BACK → FETCH → ...

这就像是计算机的"呼吸"——每个周期约 0.2 纳秒,每秒约 50 亿次"呼吸"。

阶段 1:取指(FETCH)

CPU 查看程序计数器(Program Counter, PC)——这是一个特殊的寄存器,永远存储着"下一条指令的内存地址"。

PC = 0x00400100    ← 程序计数器指向这个地址
CPU 向内存发送请求:"请把地址 0x00400100 的内容给我"
内存返回:0x4889E5  ← 这是一条 64 位的机器指令
PC = PC + 4 = 0x00400104  ← 程序计数器自动+4(因为指令是4字节)

阶段 2:译码(DECODE)

CPU 的指令译码器收到 0x4889E5(十进制 4753893),将其翻译为人类能理解的形式:

0x4889E5 = 0100 1000 1000 1001 1110 0101(二进制)
操作码(opcode):0100 1000 → "MOV"(数据移动指令)
源操作数:RBP 寄存器
目标操作数:RSP 寄存器
语义:MOV RSP, RBP  ← "将 RBP 的内容复制到 RSP"

阶段 3:执行(EXECUTE)

根据译码结果,CPU 的 ALU 执行具体操作:

ALU 从 RBP 寄存器中读取值:0x7FFF0000
ALU 将该值写入 RSP 寄存器:RSP = 0x7FFF0000

如果没有 ALU 操作(如本例的纯数据移动),则由"旁路电路"直接将值路由过去。

阶段 4:写回(WRITE BACK)

如果指令需要将结果写回内存(而非寄存器),则在此阶段执行:

CPU 将计算结果 0x02A3 写入内存地址 0x7FFF0010
内存响应:"写入完成"

至此,一个指令周期完成。程序计数器自动指向下一条指令,CPU 从 FETCH 阶段重新开始——永无止境。

程序计数器:计算机的"下一步"直觉

程序计数器(PC)是冯·诺依曼架构中最关键也最巧妙的机制之一。它解决了"计算机如何知道下一步执行什么"这个问题。

正常情况下,PC 的行为很简单:每执行一条指令,PC = PC + 4(指令长度通常为 4 字节)。这是一种顺序执行模式。

但真正的程序不是"从头到尾按顺序跑"的流水账,它充满了分支和循环。这就是跳转指令的作用:

JMP 0x00400200    ← 无条件跳转到地址 0x00400200
                  PC = 0x00400200(而非 +4)

CMP RAX, 0        ← 比较 RAX 和 0
JZ 0x00400300     ← 如果 RAX == 0,PC = 0x00400300
                  ← 如果 RAX != 0,PC = PC + 4(顺序执行)

程序计数器的存在让计算机能够在没有人类指引的情况下自动运行几亿行指令。这就是自动机的本质。

技术深入:用汇编伪代码模拟指令周期的每一步

为了让你彻底理解指令周期,这里用汇编伪代码写一个完整的示例程序,模拟 CPU 在执行三条指令时的内部状态变化:

# 假设初始状态:
# PC = 0x100(程序计数器)
# R1 = 0(寄存器1)
# R2 = 0(寄存器2)
# R3 = 0(寄存器3)
# 内存[0x200] = 42(地址 0x200 存着常数 42)
# 内存[0x204] = 58(地址 0x204 存着常数 58)
# 内存[0x208] = 0 (地址 0x208 存着结果,初始为 0)

# 地址 0x100:LOAD R1, [0x200]  # 从内存地址 0x200 读取值到 R1
# 地址 0x104:LOAD R2, [0x204]  # 从内存地址 0x204 读取值到 R2
# 地址 0x108:ADD R3, R1, R2    # R3 = R1 + R2
# 地址 0x10C:STORE [0x208], R3 # 将 R3 的值写回内存 0x208
# 地址 0x110:HALT               # 停止

# ==== 周期 1:执行 LOAD R1, [0x200] ====
# FETCH: 从地址 PC=0x100 取出指令字节 → 译码器
# DECODE:操作码=LOAD, 目标寄存器=R1, 源地址=0x200
# EXECUTE:CPU 发送读请求到地址总线 0x200 → 内存返回 42
# WRITE BACK:R1 = 42(写入寄存器)
# PC = 0x100 + 4 = 0x104
# 状态:PC=0x104, R1=42, R2=0, R3=0

# ==== 周期 2:执行 LOAD R2, [0x204] ====
# FETCH: 从地址 PC=0x104 取出指令字节 → 译码器
# DECODE:操作码=LOAD, 目标寄存器=R2, 源地址=0x204
# EXECUTE:CPU 发送读请求到地址总线 0x204 → 内存返回 58
# WRITE BACK:R2 = 58
# PC = 0x104 + 4 = 0x108
# 状态:PC=0x108, R1=42, R2=58, R3=0

# ==== 周期 3:执行 ADD R3, R1, R2 ====
# FETCH: 从地址 PC=0x108 取出指令字节 → 译码器
# DECODE:操作码=ADD, 目标寄存器=R3, 源寄存器1=R1, 源寄存器2=R2
# EXECUTE:ALU 计算 R1 + R2 = 42 + 58 = 100
# WRITE BACK:R3 = 100
# PC = 0x108 + 4 = 0x10C
# 状态:PC=0x10C, R1=42, R2=58, R3=100

# ==== 周期 4:执行 STORE [0x208], R3 ====
# FETCH: 从地址 PC=0x10C 取出指令字节 → 译码器
# DECODE:操作码=STORE, 源寄存器=R3, 目标地址=0x208
# EXECUTE:CPU 发送写请求到地址总线 0x208,数据=100
# WRITE BACK:内存[0x208] = 100
# PC = 0x10C + 4 = 0x110
# 状态:PC=0x110, 内存[0x208]=100

# ==== 周期 5:执行 HALT ====
# FETCH: 从地址 PC=0x110 取出指令字节
# DECODE:操作码=HALT
# EXECUTE:停止执行,拉低 HALT 信号线
# 程序结束。最终结果:内存地址 0x208 存储着 100(42 + 58 的和)

这个简单的 5 步过程,在真实的 x86-64 CPU 上会在约 1 纳秒内完成(5 个周期 x 0.2 纳秒/周期)。而你的手机 CPU 每秒钟可以执行约 150 亿个这样的周期。

寄存器:CPU 内部的"超高速草稿纸"

细心的读者可能已经注意到上述伪代码中反复出现 R1R2R3——这些就是寄存器(Registers)。寄存器是 CPU 内部的一小撮超级快速存储单元,是解除(缓解)冯·诺依曼瓶颈最直接的手段。

CPU 架构通用寄存器数量字长说明
Intel 4004(1971)16 个 4 位寄存器4 位世界上第一个微处理器
Intel 8086(1978)8 个 16 位寄存器(AX/BX/CX/DX 等)16 位IBM PC 的大脑
ARM32(1985)16 个 32 位寄存器(R0-R15)32 位RISC 经典设计
x86-64(2003)16 个 64 位寄存器(RAX/RBX/RCX/RDX + R8-R15)64 位现代桌面/服务器
ARM64(2011)31 个 64 位寄存器(X0-X30)64 位手机/平板/苹果 M 系列
RISC-V(2010)32 个寄存器(x0-x31)32/64/128 位开源架构

寄存器与各级存储的速度对比(以 3.5 GHz CPU 的时钟周期为单位):

存储层级延迟(时钟周期)延迟(纳秒)容量(典型值)
寄存器1 周期~0.25 ns~1 KB
L1 缓存4-5 周期~1 ns32-64 KB
L2 缓存12-14 周期~3-4 ns256-512 KB
L3 缓存40-50 周期~12 ns8-32 MB
主内存(RAM)200-300 周期~60-100 ns8-64 GB
NVMe SSD~50,000 周期~15 μs256 GB - 4 TB
SATA SSD~500,000 周期~150 μs256 GB - 8 TB
HDD~10,000,000 周期~3-10 ms1-20 TB

比喻:如果寄存器的访问时间是你眨一下眼(0.1 秒),那么访问硬盘就像等整整 27 天。

工具箱:用 Python 模拟最简单的冯·诺依曼虚拟机

最好的理解方式是亲手实现。以下是一个约 80 行的 Python 程序,模拟了一个极简的冯·诺依曼架构虚拟机:

"""
mini-von-neumann.py — 一个极简的冯·诺依曼架构虚拟机模拟器
支持的操作码(opcode):LOAD, STORE, ADD, SUB, JMP, JZ, HALT
"""

class MiniVM:
    def __init__(self, memory_size=256):
        # 统一存储器:程序和数据存在同一个数组里(冯·诺依曼的核心思想!)
        self.memory = [0] * memory_size
        self.registers = [0] * 4       # 4 个通用寄存器 R0-R3
        self.pc = 0                     # 程序计数器 Program Counter
        self.running = True
        self.zero_flag = False          # 零标志位(用于条件跳转)

        # 操作码定义(程序也是数据!)
        self.OPS = {
            0x01: ('LOAD',  self.op_load),
            0x02: ('STORE', self.op_store),
            0x03: ('ADD',   self.op_add),
            0x04: ('SUB',   self.op_sub),
            0x05: ('JMP',   self.op_jmp),
            0x06: ('JZ',    self.op_jz),
            0xFF: ('HALT',  self.op_halt),
        }

    def load_program(self, program):
        """将程序(一串数字)加载到存储器开头"""
        for i, byte in enumerate(program):
            self.memory[i] = byte
        print(f"[VM] 程序已加载,{len(program)} 字节,从地址 0 开始")
        print(f"[VM] 程序原始数据: {program}")

    def fetch(self):
        """取指阶段:从 PC 指向的存储器地址取一条指令"""
        if self.pc >= len(self.memory):
            self.running = False
            return None, None
        opcode = self.memory[self.pc]
        # 指令格式: [opcode][operand1][operand2][operand3]
        operands = (
            self.memory[self.pc + 1] if self.pc + 1 < len(self.memory) else 0,
            self.memory[self.pc + 2] if self.pc + 2 < len(self.memory) else 0,
            self.memory[self.pc + 3] if self.pc + 3 < len(self.memory) else 0,
        )
        return opcode, operands

    def run(self):
        """主循环:FETCH → DECODE → EXECUTE → PC+=4 → FETCH ..."""
        print("\n[VM] === 开始执行 ===")
        cycle = 0
        while self.running:
            # === FETCH ===
            opcode, operands = self.fetch()
            if opcode is None:
                break

            # === DECODE + EXECUTE ===
            op_name, handler = self.OPS.get(opcode, ('UNKNOWN', None))
            if handler is None:
                print(f"[VM] 未知操作码 {opcode:02X},停止")
                break

            self.pc += 4  # 每条指令 4 字节,PC 自动递增
            handler(operands)

            cycle += 1
            # 安全阀:防止无限循环
            if cycle > 1000:
                print("[VM] 超过 1000 个周期,强制停止")
                break

        print(f"[VM] === 执行完成,共 {cycle} 个周期 ===\n")
        self.dump_state()

    # --- 指令实现 ---
    def op_load(self, ops):
        reg, addr = ops[0], ops[1]
        self.registers[reg] = self.memory[addr]
        print(f"  LOAD  R{reg} ← [{addr}] = {self.registers[reg]}")

    def op_store(self, ops):
        reg, addr = ops[0], ops[1]
        self.memory[addr] = self.registers[reg]
        print(f"  STORE [{addr}] ← R{reg} = {self.registers[reg]}")

    def op_add(self, ops):
        dst, src1, src2 = ops[0], ops[1], ops[2]
        self.registers[dst] = self.registers[src1] + self.registers[src2]
        self.zero_flag = (self.registers[dst] == 0)
        print(f"  ADD   R{dst} ← R{src1}({self.registers[src1]})"
              f" + R{src2}({self.registers[src2]}) = {self.registers[dst]}")

    def op_sub(self, ops):
        dst, src1, src2 = ops[0], ops[1], ops[2]
        self.registers[dst] = self.registers[src1] - self.registers[src2]
        self.zero_flag = (self.registers[dst] == 0)
        print(f"  SUB   R{dst} ← R{src1}({self.registers[src1]})"
              f" - R{src2}({self.registers[src2]}) = {self.registers[dst]}")

    def op_jmp(self, ops):
        self.pc = ops[0]
        print(f"  JMP   → PC = {self.pc}")

    def op_jz(self, ops):
        if self.zero_flag:
            self.pc = ops[0]
            print(f"  JZ    (flag=1) → PC = {self.pc}")
        else:
            print(f"  JZ    (flag=0) → 不跳转")

    def op_halt(self, ops):
        print(f"  HALT")
        self.running = False

    def dump_state(self):
        print(f"[VM] 最终状态:")
        print(f"     PC = {self.pc}")
        for i, v in enumerate(self.registers):
            print(f"     R{i} = {v}")
        # 打印内存中非零的值
        nonzero = {addr: val for addr, val in enumerate(self.memory) if val != 0}
        print(f"     非零内存单元: {nonzero}")


# ===== 示例程序:计算 10 + 20 × 3 = 70 =====
# 内存布局:
#   地址 0x00-0x03: 第一条指令(LOAD R0, [DATA_10])
#   地址 0x04-0x07: 第二条指令(LOAD R1, [DATA_20])
#   地址 0x08-0x0B: 第三条指令(LOAD R2, [DATA_3])
#   地址 0x0C-0x0F: 第四条指令(ADD R1, R1, R1)——注意:这里用重复加法模拟乘法,实际应该用 MUL
#   地址 0x10-0x13: 第五条指令(ADD R1, R1, R2)——修正:正确模拟 R1 = R1 + R2 的思路
#   ...实际上为了简洁,直接用三次加法:R0=10, R1=20, R2=3 → 20+20+20=60 → 60+10=70
#   地址 0x14-0x17: HALT
#   地址 0x50: DATA_10 = 10
#   地址 0x51: DATA_20 = 20
#   地址 0x52: DATA_3 = 3

program = [
    # 指令编码: [opcode, operand1, operand2, operand3]
    0x01, 0, 0x50, 0,     # [0x00] LOAD R0, [0x50]  → R0 = 10
    0x01, 1, 0x51, 0,     # [0x04] LOAD R1, [0x51]  → R1 = 20
    0x03, 1, 1, 1,        # [0x08] ADD  R1, R1, R1  → R1 = 40 (20+20)
    0x03, 0, 0, 1,        # [0x0C] ADD  R0, R0, R1  → R0 = 50 (10+40)
    0x03, 0, 0, 1,        # [0x10] ADD  R0, R0, R1  → R0 = 90 (50+40) ...不对
    0xFF, 0, 0, 0,        # [0x14] HALT
]
# 简化版:直接算 10 + 20 + 3 = 33
program_v2 = [
    0x01, 0, 0x50, 0,     # LOAD R0, [0x50]  → R0 = 10
    0x01, 1, 0x51, 0,     # LOAD R1, [0x51]  → R1 = 20
    0x03, 0, 0, 1,        # ADD  R0, R0, R1  → R0 = 30
    0x01, 1, 0x52, 0,     # LOAD R1, [0x52]  → R1 = 3
    0x03, 0, 0, 1,        # ADD  R0, R0, R1  → R0 = 33
    0x02, 0, 0x60, 0,     # STORE [0x60], R0 → 内存[0x60]=33
    0xFF, 0, 0, 0,        # HALT
]

# 在内存数据区预置数据
memory_data = {0x50: 10, 0x51: 20, 0x52: 3}

vm = MiniVM(256)
for addr, val in memory_data.items():
    vm.memory[addr] = val
vm.load_program(program_v2)
vm.run()

# 预期输出: 内存[0x60] = 33
print(f"验证: 内存[0x60] = {vm.memory[0x60]} (期望: 33)")

将此代码保存为 mini-von-neumann.py 并运行 python3 mini-von-neumann.py,你会看到虚拟机的每一步执行过程。你还可以修改程序,添加自己的指令,体验"存储程序"概念的实际运作。

番外篇:为什么至今 99.99% 的计算机仍然使用冯·诺依曼架构?

答案是:简单就是最大的优势

  1. 统一存储器简化了编译器设计:编译器不需要区分"程序内存"和"数据内存",只需生成一个统一地址空间内的代码
  2. 自修改代码成为可能:调试器(如 GDB/LLDB)能修改程序指令来设置断点,这依赖程序存储在可写内存中
  3. 操作系统的基础:程序加载器只需将可执行文件从磁盘复制到内存,然后设置 PC 指向入口地址——如果程序和数据存储分离,这个过程会复杂得多
  4. 动态链接和虚拟机(JVM/.NET CLR):所有这些依赖运行时加载和修改代码的技术,都建立在"程序在可写内存中"的前提上
  5. 生态惯性:所有现有的操作系统、编译器、开发工具都基于冯·诺依曼架构设计。改架构的成本大到不可想象

但这并不意味着"永远不变"。近年来,存内计算(Processing-in-Memory, PIM)和神经形态计算正在挑战冯·诺依曼架构的统治地位。我们将在第六章深入讨论这些前沿话题。

金句

“冯·诺依曼架构不是最优的,但它是最简单、最通用的。在工程中,通用性往往比最优性更重要——因为世界不需要一台完美解线性方程的机器,它需要一台能运行任何程序的机器。”


3.3 哈佛架构:另一种可能

哈佛 Mark I:一吨重的机电巨兽

1944 年,几乎与 ENIAC 同时,哈佛大学的霍华德·艾肯(Howard Aiken)完成了另一台巨型计算机——哈佛 Mark I(正式名称为"IBM 自动序列控制计算器",ASCC)。

Mark I 是一头不同的野兽:

特性ENIACHarvard Mark I
完成时间1945 年 12 月1944 年 8 月
计算元件17,468 个真空管765,000 个机电继电器
重量30 吨5 吨
长度30 米16 米
运算速度每秒 5,000 次加法每秒 3 次加法
数据表示十进制(10 位)十进制(23 位精度)
编程方式手动接线穿孔纸带(指令纸带 + 数据纸带——两条独立的纸带!)

请注意最后一行——Mark I 使用两条独立的纸带:一条用于输入指令(程序),一条用于输入数据。这就是哈佛架构的起源。

霍华德·艾肯是一个有争议的人物。他拒绝使用真空管(因为"不可靠"),坚持使用机电继电器,导致 Mark I 从诞生那天起就已过时。当有人告诉他 ENIAC 快 1000 倍时,他回应:“有无数问题用不着那么快的速度。”

这句话后来被艾肯本人称为"我职业生涯中说过的最愚蠢的话"。

哈佛架构的核心:程序与数据的物理分离

哈佛架构的核心理念可以用一句话概括:

程序存储器和数据存储器使用不同的物理总线、不同的地址空间和不同的存储设备。

这意味着:

冯·诺依曼架构:           哈佛架构:
                          
CPU ←→ [程序+数据]       CPU ←→ [程序]   (指令总线)
  单总线                      ↕
                         CPU ←→ [数据]   (数据总线)
                            双总线

在实际实现中,哈佛架构的关键特征包括:

  1. 独立的地址空间:程序存储器中的地址 0x100 和数据存储器中的地址 0x100 指向不同的物理位置
  2. 不同的位宽:程序存储器可以比数据存储器宽(例如,16 位指令 + 8 位数据),优化指令密度
  3. 同时访问:CPU 可以在一个时钟周期内同时取指令和取数据——因为走的是两根不同的总线
  4. 不同的存储技术:程序存储器可以用只读 ROM,数据存储器用可读写 RAM

冯·诺依曼 vs 哈佛:设计哲学的分歧

维度冯·诺依曼架构哈佛架构
存储器结构统一存储器程序/数据物理分离
总线数量1 条(共享)2 条(独立)
取指 + 取数不能同时(瓶颈)可以同时(无瓶颈)
编程灵活性高(程序即数据)低(程序和数据分开管理)
编译器复杂度低(统一地址空间)高(需管理两个地址空间)
安全性低(缓冲区溢出可覆盖代码)高(数据区无法修改代码)
自修改代码支持不支持
典型应用桌面/服务器/手机DSP、微控制器、嵌入式系统

为什么哈佛架构天然防缓冲区溢出?

这是一个非常巧妙的特性。考虑以下 C 语言代码中的典型缓冲区溢出漏洞:

// 冯·诺依曼架构下的缓冲区溢出攻击
void vulnerable_function() {
    char buffer[64];        // 在栈上分配 64 字节
    gets(buffer);           // 危险!没有边界检查
    // 攻击者输入超过 64 字节 → 覆盖栈上的返回地址
    // → 程序执行流程被劫持 → 执行恶意代码
}

在冯·诺依曼架构中,buffer(数据)和返回地址(“程序控制信息”)都在同一片内存中——栈。溢出 buffer 可以覆盖返回地址,从而改变程序执行流程。这是 90% 以上安全漏洞的根源(缓冲区溢出、ROP 攻击、格式化字符串漏洞等)。

在哈佛架构中:栈(数据)存放在数据存储器中,而代码(包括所有程序逻辑)存放在指令存储器中。即使攻击者溢出了数据存储器中的 buffer,也绝不可能触及指令存储器中的任何东西。这是一种架构级的安全隔离

当然,哈佛架构也不能防御所有攻击——例如,攻击者仍然可能通过破坏函数指针(存储在数据存储器中)来劫持程序逻辑。但它从根本上消除了"注入代码"类攻击。

现代 DSP 和微控制器中的哈佛架构

纯粹的哈佛架构在通用计算机中很少见,但在专用领域仍然广泛使用:

数字信号处理器(DSP)

  • Texas Instruments TMS320 系列
  • Analog Devices SHARC 和 Blackfin
  • 为什么?DSP 需要每个时钟周期同时:取一条指令 + 读一个信号采样值 + 写一个计算结果。哈佛架构的双总线是天然的流水线设计

微控制器(MCU)

  • Atmel AVR(Arduino 的核心)
  • Microchip PIC
  • Intel 8051 及其衍生品
  • 为什么?程序烧录在 Flash ROM 中,运行时不需要修改代码,数据放在 SRAM 中,两者天然分离

你的汽车里有几十个哈佛架构的芯片:引擎控制单元(ECU)、防抱死制动系统(ABS)、安全气囊控制器——这些系统需要在确定的时间内完成计算,哈佛架构的可预测性比冯·诺依曼架构更好。

修改的哈佛架构:现代 CPU 的"精神分裂"

现代高性能 CPU(Intel Core、AMD Ryzen、Apple M 系列)既不是纯粹的冯·诺依曼架构,也不是纯粹的哈佛架构。它们使用修改的哈佛架构(Modified Harvard Architecture):

         CPU 核心
        /        \
    L1 指令缓存    L1 数据缓存
      (8路组相联)   (8路组相联)
        |            |
        └─────┬──────┘
              |
          L2 统一缓存
              |
          L3 统一缓存 (共享)
              |
         主内存 (DRAM) — 统一

在缓存层面,指令和数据是分开的(哈佛式),但在主存层面是统一的(冯·诺依曼式)。这种设计的优势:

  • 性能:CPU 可以在同一周期从 L1-I 取指令、从 L1-D 取数据(哈佛的并行优势)
  • 灵活性:程序和数据在内存中是统一的,可以被同一个编译器和操作系统管理(冯·诺依曼的编程优势)
  • 缓存一致性:硬件自动处理 I-cache 和 D-cache 与统一内存之间的一致性(通过 MESI 协议等)

这就是工程中的"鱼和熊掌兼得"。

技术深入:三种架构的并行能力对比

当指令和数据访问发生冲突时,三种架构的行为截然不同:

冯·诺依曼(无缓存):

周期 1: FETCH 指令         ← 占用总线
周期 2: 译码
周期 3: FETCH 操作数        ← 占用总线(指令和数据不能同时取)
周期 4: EXECUTE
周期 5: WRITE BACK           ← 占用总线
共 5 个周期,其中 3 个周期在等总线

纯哈佛

周期 1: FETCH 指令 + FETCH 操作数 ← 两条总线同时工作!
周期 2: EXECUTE + WRITE BACK      ← 第二条总线在写回时又能同时取下一条指令
共 2 个周期

改进哈佛(有缓存,L1-I/L1-D 命中):

周期 1: FETCH 指令(L1-I) + FETCH 操作数(L1-D) ← 两条独立缓存总线
周期 2: EXECUTE + FETCH 下一条指令(L1-I)      ← 流水线重叠
共约 1-2 个周期

这就是为什么改进哈佛架构能将 IPC(每周期指令数)做到 > 1——现代超标量处理器通常能达到 IPC = 2-4。

流水线:从串行厨房到并行餐厅

指令周期(FETCH → DECODE → EXECUTE → WRITE BACK)的天然问题是:每个阶段只使用 CPU 的一部分硬件。在 FETCH 阶段,ALU 是空闲的;在 EXECUTE 阶段,取指单元是空闲的。这就像一个大厨房里,厨师在切菜时,炉灶在闲着;炉灶在炒菜时,厨师在闲着。

流水线(Pipelining)解决了这个问题。它把指令周期想象成一个工厂流水线:

时钟周期:  1     2     3     4     5     6     7     8
指令1:   FETCH DECODE EXEC WB
指令2:         FETCH DECODE EXEC WB
指令3:               FETCH DECODE EXEC WB
指令4:                     FETCH DECODE EXEC WB
指令5:                           FETCH DECODE EXEC WB

在没有流水线的情况下,5 条指令需要 5 x 4 = 20 个时钟周期。有了流水线,5 条指令只需要 8 个时钟周期——吞吐量提升了 2.5 倍

但流水线有一个致命的弱点:分支(Branch)。当 CPU 遇到 if (x > 0) { A } else { B },它不知道应该把 A 还是 B 装入流水线。如果猜错了,已经装入流水线的指令全部作废——这叫流水线冲刷(Pipeline Flush),代价是 10-20 个时钟周期。

这就引出了分支预测器(Branch Predictor)——现代 CPU 中最精妙的组件之一。分支预测器通过历史模式来"猜测"分支的方向。现代分支预测器(如 Intel 的 TAGE 预测器或 AMD 的感知器预测器)可以达到 95-98% 的准确率——这意味着 100 次分支中只有 2-5 次预测错误。

超标量:不止一条流水线

如果一条流水线能让吞吐量翻倍,那多条流水线呢?这就是超标量(Superscalar)架构——CPU 同时运行多条流水线,每个时钟周期发射多条指令:

时钟周期:  1         2         3         4
流水线1:  FETCH-A   DECODE-A  EXEC-A    WB-A
流水线2:  FETCH-B   DECODE-B  EXEC-B    WB-B
流水线3:  FETCH-C   DECODE-C  EXEC-C    WB-C
流水线4:  FETCH-D   DECODE-D  EXEC-D    WB-D

现代高端 CPU 的发射宽度(每个周期发射的指令数):

  • Apple M4:10 宽度(2024 年,业界最宽)
  • Intel Core Ultra 9:8 宽度
  • AMD Ryzen 9 9950X:6 宽度
  • ARM Cortex-X4:8 宽度

但超标量面临着"指令级并行度"(ILP)的天然限制——不是所有指令都能并行执行。如果指令 B 依赖指令 A 的结果,它们就不能同时执行。编译器通过指令调度(Instruction Scheduling)来最大化 ILP,但根本上受限于程序本身的依赖关系。

乱序执行:CPU 的"时间管理大师"

在冯·诺依曼的原始设计中,指令必须按顺序执行——第 1 条、第 2 条、第 3 条……依次执行。但现代 CPU 是乱序执行(Out-of-Order Execution, OoOE)的——CPU 可以在指令流中"跳跃",先执行不依赖于前面指令结果的指令。

这就像你是一个忙碌的项目经理,手上有 10 个任务,其中 5 个在等外部供应商的回复。你会坐在那里等那 5 个任务,还是先做另外 5 个不需要等待的任务?现代 CPU 选择了后者。

乱序执行的步骤

  1. 取指:按顺序取指令(PC → PC+4 → PC+8)
  2. 发射:将指令放入保留站(Reservation Station),分析依赖关系
  3. 执行:一旦某条指令的操作数都准备好了,立即执行——不管它在指令流中的顺序
  4. 写回:结果写入重排序缓冲区(Reorder Buffer, ROB)
  5. 提交:按原始顺序将结果写回寄存器——从外部看,指令还是按顺序完成的

这个机制确保了即使内部执行乱序,外部观察到的程序状态仍然是顺序一致的——这是"冯·诺依曼语义"的核心保证。

金句

“冯·诺依曼的单一总线是计算机世界的曼哈顿大桥——所有车辆都要排队通过。哈佛的双总线是双层桥——轿车走上层,货车走下层,同行不堵。”


3.4 存储技术的演变:从延迟线到 SSD

计算机的本质是什么?是计算?不,计算是手段。计算机的本质是信息的存储与变换。没有存储器,CPU 每秒能做一万亿次运算也没用——因为每次算完的结果都必须保存在某个地方。

存储技术的演变史,就是人类不断寻找"更快、更大、更便宜、更耐久"的信息容器。

第一阶段:声音的记忆——水银延迟线(1940s)

EDA(电子延迟存储自动计算器)和 EDSAC 使用的第一种电子存储器,听起来像一个音响发烧友的疯狂实验。

原理:水银延迟线是一根长约 1.5 米的水银管,两端各有一个石英晶体换能器。电脉冲在发送端转换为超声波,通过水银传播(声波在水银中约 1450 m/s),到达接收端后重新转换为电脉冲,再通过放大器送回发送端——形成一个循环。

数据以"声波脉冲串"的形式在水银管中循环。一根管子可以存储约 500-1000 个比特(具体取决于脉冲频率和管长)。要读取某个特定的比特,你必须等它"走"到接收端——平均等待时间约 0.5 毫秒。

[电脉冲] → [石英换能器] → 超声波在水银中传播(~1ms) → [石英换能器] → [放大器] → [电脉冲] → 循环

缺点清单

  • 温度敏感:水银的热膨胀会改变延迟时间,导致数据丢失——所以有人在计算机房里"抱着"延迟线调节温度
  • 有毒:水银蒸气有毒,维护时需要特殊的防护
  • 慢:访问时间以毫秒计,与现代纳秒级差距 6 个数量级
  • 串行访问:只能按序读取,不能随机访问

但它们确实能工作。EDSAC(1949 年)使用水银延迟线作为主存,成功运行了世界上第一个实用的存储程序——这证明了冯·诺依曼架构的可行性。

第二阶段:光的记忆——威廉姆斯管(1940s-1950s)

如果说水银延迟线是"声学存储",威廉姆斯管就是"光学存储"——但有一个奇怪的转折。

原理:威廉姆斯管本质上是一台改装的 CRT(阴极射线管,即老式电视机的显像管)。电子束打在荧光屏上时,被击中的点会带静电——这个电荷可以维持约 0.2 秒,然后逐渐泄漏。如果在电荷泄漏之前重新"刷新"(再打一次电子束),数据就被保持住了。

读取方式更为巧妙:在目标点旁边打一束弱电子束,测量其偏离程度——如果在目标点有电荷,弱电子束会被排斥;如果没有电荷,弱电子束直线通过。这被称为"存储效应"。

[写入] → 电子束打向屏幕 → 目标点带电("1")或不带电("0")
[读取] → 弱电子束探测 → 偏转=有电荷="1"/直行=无电荷="0"
[刷新] → 在电荷泄漏前(<0.2s)重新写入

一根 CRT 管可以存储约 1024-2048 个比特(32x32 或 64x32 的"点阵")。IBM 701(1952 年)使用了 72 根威廉姆斯管作为主存储器,总容量 2048 个 36 位字——约 9 KB。

威廉姆斯管有一个著名的"副作用":你在屏幕上可以看到存储的数据——一个在不断闪烁的光点图案。熟练的操作员甚至能"看出"程序是否陷入死循环。

第三阶段:磁芯存储器——王安的突破(1950s-1970s)

磁芯存储器(Magnetic Core Memory)是存储技术的第一个真正的"革命",它的发明者是美籍华裔工程师王安(An Wang)。

原理:每一个比特存储在一个小小的铁氧体磁环(磁芯)中——直径约 1 毫米。磁芯可以被磁化为两个方向:顺时针 = “1”,逆时针 = “0”。磁芯阵列由交织的 X/Y 导线编织而成(像一个微型的地毯编织机):

        Y1  Y2  Y3  Y4
        |   |   |   |
   X1 — ⊕ — ⊕ — ⊕ — ⊕    ← ⊕ = 一个磁芯,X/Y 线交叉处
        |   |   |   |
   X2 — ⊕ — ⊕ — ⊕ — ⊕
        |   |   |   |
   X3 — ⊕ — ⊕ — ⊕ — ⊕

要读写某个磁芯,同时给它所在的 X 线和 Y 线通一半电流——只有交叉点的磁芯会收到完整的电流,从而翻转磁化方向。这是一种电控矩阵随机访问,不需要等待某个"位置"旋转到——你可以直接访问任何地址。

王安的突破在于"读后写"(read-after-write)机制。当磁芯被读取时(被强制磁化到"0"方向),如果它原来是"1",磁化翻转会产生一个感应电流脉冲——通过第三根"感应线"检测到;如果它原来是"0",就没有脉冲。但读取操作会破坏原始数据(都变成了"0"),所以读取后必须立即重写。这正是现代 DRAM 的工作原理。

王安的职业生涯堪称一个"美国梦"的完整故事

  • 1945 年:带着 600 美元从上海来到哈佛大学攻读应用物理学博士
  • 1948 年:在哈佛计算实验室发明磁芯存储器,申请专利
  • 1951 年:创立王安实验室(Wang Laboratories)
  • 1955 年:将磁芯存储器专利以 50 万美元卖给 IBM(IBM 随后将其用于所有大型机)
  • 1960-1970 年代:王安电脑公司推出王安 2200 小型机、文字处理系统,成为美国最大的华人企业
  • 1984 年:年收入 23 亿美元,王安个人资产 16 亿美元,位列美国第五大富豪
  • 1986 年:获得美国总统自由勋章
  • 1990 年:因拒绝兼容 IBM PC 标准,公司濒临破产。王安因食道癌去世,享年 70 岁
  • 1992 年:王安电脑公司申请破产保护

王安的悲剧是一个鲜活的商业教训:技术上的正确决定(不兼容 IBM PC 的劣质架构)可能是商业上的致命决定。他的儿子弗雷德·王安接管公司后无力回天——这又是一个家族企业传承管理的经典反面案例。

第四阶段:磁带与磁盘——当 5MB 需要两个冰箱(1956-)

1956 年,IBM 推出了 RAMAC 305(Random Access Method of Accounting and Control),这是世界上第一台使用磁盘存储的计算机。

RAMAC 的磁盘

  • 容量:5 MB(是的,5 兆字节,不是 GB,更不是 TB)
  • 物理尺寸:两个大型冰箱并排大小(约 1.5m x 1.7m x 0.7m)
  • 盘片:50 片,直径 24 英寸(约 61 厘米),铝制,涂有氧化铁
  • 转速:1200 RPM
  • 数据传输率:每秒 8,800 个字符(约 8.8 KB/s)
  • 价格:每月租赁费约 3,200 美元(按通胀调整约合现在 35,000 美元/月)

折算一下:存储 1GB 的成本约 640,000 美元/月(按当时价格),而且需要一个网球场大小的空间。

IBM RAMAC 之后的关键里程碑

年份产品容量关键创新
1956IBM RAMAC 3505 MB世界上第一个磁盘
1962IBM 13112 MB/盘片可更换盘片(disk pack)
1971IBM “温彻斯特” 334035-70 MB密封式磁头-盘片组件(现代 HDD 的鼻祖)
1980Seagate ST-5065 MB第一款 5.25" 微机硬盘($1,500)
1991IBM 0663 “Corsair”1 GB第一款超过 1GB 的 3.5" 驱动器 ($1,200)
2005Hitachi Deskstar 7K500500 GB垂直磁记录(PMR)首次商用
2024Seagate Exos Mozaic 3+30 TBHAMR(热辅助磁记录)+ 10 盘片

第五阶段:半导体存储器——从晶体管到 3D NAND(1970s-至今)

当磁芯存储器仍在广泛使用时,一种全新的存储技术正在贝尔实验室和仙童半导体公司的实验室中酝酿——半导体存储器

DRAM(动态随机访问存储器)——1970 年,Intel 1103

世界上第一款商用 DRAM 芯片:Intel 1103,容量 1024 位(128 字节),售价 10 美元,由罗伯特·登纳德(Robert Dennard)发明。

DRAM 的工作原理:每个比特存储在一个微小的电容器中。有电荷 = 1,无电荷 = 0。但电荷会逐渐泄漏(这就是"动态"的含义),所以需要每 64 毫秒刷新一次。

SRAM(静态随机访问存储器):使用 6 个晶体管构成一个触发器来存储 1 个比特。比 DRAM 快得多(不用等待刷新周期),但也贵得多(6 个晶体管 vs 1 个晶体管+1 个电容),容量小得多。主要用于 CPU 缓存。

NAND Flash——1984 年,东芝,冈村正美

Flash 存储器的关键创新是:掉电后数据不丢失(非易失性),可以整块擦除(不像 EEPROM 那样逐字节擦除)。名称"Flash"来自其擦除方式——“像闪光灯一样快”。

3D NAND 的革命:2013 年,三星推出了第一款 3D NAND(也称为 V-NAND),将存储单元从平面堆叠改为垂直堆叠——就像从"平房"变成"摩天大楼"。

层数典型容量/芯片年份
平面 NAND1 层128 Gb~2012
第一代 3D24 层128 Gb2013
第二代 3D32 层256 Gb2014
第三代 3D48 层256 Gb2015
第四代 3D64 层512 Gb2017
第五代 3D96 层1 Tb2018
第六代 3D128 层1 Tb2020
第七代 3D176 层1 Tb2022
第八代 3D232 层2 Tb2024
第九代 3D300+ 层2+ Tb2025(规划中)

从 1 层到 300+ 层,11 年间存储密度的提升超过了 300 倍——这个速度甚至超过了摩尔定律。

技术深入:存储器层次结构全景

冯·诺依曼瓶颈的工程解决方案就是存储器层次结构(Memory Hierarchy)。这是一个"用空间换时间"的经典设计:

    速度更快、容量更小、单位成本更高
    ┌──────────────────────────────────────┐
    │  寄存器 (0.25ns, ~1KB)               │ ← CPU 内部
    │  ┌──────────────────────────────┐    │
    │  │ L1 缓存 (1ns, 64KB)          │    │ ← CPU 内部
    │  │ ┌──────────────────────┐     │    │
    │  │ │ L2 缓存 (3-4ns, 512KB)│    │    │ ← CPU 封装内
    │  │ │ ┌──────────────┐     │     │    │
    │  │ │ │L3 缓存       │     │     │    │ ← CPU 封装内/外
    │  │ │ │(12ns, 16MB)  │     │     │    │
    │  │ │ └──────────────┘     │     │    │
    │  │ └──────────────────────┘     │    │
    │  └──────────────────────────────┘    │
    │                                       │
    │    主内存 RAM (60-100ns, 16GB)        │ ← 主板
    │                                       │
    │    NVMe SSD (15μs, 1TB)              │ ← PCIe 总线
    │                                       │
    │    SATA SSD (150μs, 2TB)             │ ← SATA 总线
    │                                       │
    │    HDD (3-10ms, 10TB)                │ ← SATA/SAS
    └──────────────────────────────────────┘
    速度更慢、容量更大、单位成本更低

缓存的工作原理是局部性原理(Principle of Locality):

  • 时间局部性:刚刚访问过的数据,很可能马上再次被访问(如循环计数器)
  • 空间局部性:刚刚访问过的地址附近的数据,很可能马上被访问(如数组遍历)

缓存利用这两种局部性,将最近使用过的数据和指令保存在离 CPU 更近的快速存储器中,大大减少了对慢速主存的访问次数。现代 CPU 的缓存命中率通常在 95% 以上——这意味着只有 5% 的内存访问需要走过那根"吸管"。

番外篇:消失的存储技术——你从未听说过的"失败的未来"

存储技术的发展史上充满了天才但失败的想法:

1. 磁泡存储器(Bubble Memory, 1970s)
贝尔实验室发明的一种存储器,利用石榴石薄膜中的微小磁化区域(“磁泡”)来存储数据。Intel 在 1970 年代花费了五年时间试图将其商业化(Intel 7110,1 Mb 芯片),但最终被更便宜更快的 DRAM 碾碎。Intel 在磁泡存储器上损失了约 1 亿美元(按 2025 年美元价值约 5 亿)——当时几乎是这家公司的全部身家。

2. CCD 存储器(Charge-Coupled Device, 1970s)
不是相机里的 CCD!事实上,仙童半导体和英特尔都曾试图用 CCD(电荷耦合器件)来做计算机存储器。原理:CCD 像一个"电荷斗链"——通过依次传递电荷包来存储和移动数据。Sperry Univac 甚至在 1978 年推出了一台使用 CCD 存储器的计算机,但 DRAM 的进步让 CCD 存储迅速破产。

3. 全息存储(Holographic Storage, 1990s-2010s)
利用激光干涉图案在光敏晶体中存储三维数据。理论潜力:一张 DVD 大小的碟片可以存储数 TB。IBM、索尼、日立都投入了大量资金,但从未成功商业化。2024 年,微软的 Project Silica 仍在使用类似概念(在石英玻璃中存储数据),但目标已从"高性能"变为"超长期归档"(1000 年+保存期)。

这些失败说明了什么?
技术史不是"最好的技术获胜",而是"在正确的时机以正确的成本和可靠性出现的技术"获胜。DRAM 在技术上不如磁泡存储器优雅,但它便宜、容易制造、与现有的半导体制造工艺兼容。这就是"足够好"打败"完美"的经典案例。

存储的未来:从 3D NAND 到 DNA 存储

当前存储技术的最前沿正在同时向两个极端发展——更快和更持久:

SCM(存储级内存,Storage Class Memory)
英特尔在 2019 年推出了傲腾持久内存(Optane Persistent Memory),它位于 DRAM 和 SSD 之间的性能/价格/容量甜区——比 SSD 快 10 倍,比 DRAM 容量大 10 倍,且断电后数据不丢失。然而,由于未能达到量产经济性,英特尔在 2022 年关闭了傲腾业务。这是一个"技术上成功但商业上失败"的悲剧故事。

DNA 存储
哈佛大学的乔治·丘奇(George Church)团队在 2012 年将一本 5.34 万字的书(包括 11 张图片和 1 个 JavaScript 程序)编码为 DNA 序列并成功读取。1 克 DNA 理论上可以存储 215 PB(拍字节,约 2.25 亿 GB)——这意味着整个互联网的数据理论上可以装进一个鞋盒。主要瓶颈:读取速度(目前需要数小时到数天)和成本(编码和测序仍然很昂贵)。

石英玻璃存储
微软的 Project Silica 使用飞秒激光在石英玻璃中写入纳米级刻痕(一种"体素",体素可以是多层结构,一个 2mm 厚的玻璃片能存 100 层以上),通过偏振显微镜读取。数据保存时间:10,000 年以上——远超任何现有存储介质。主要瓶颈:写入后不可擦除(WORM,一次写入多次读取),不适合频繁修改的数据。

这些前沿技术虽然在 2025 年还没有进入消费市场,但它们指向了一个共同的趋势:存储密度将持续提高,而存储介质将更加多样化——计算和存储的边界将进一步模糊。

金句

“存储器的历史是人类不断对抗物理极限的历史:用水银存声波,用 CRT 存光影,用磁芯存磁力,用芯片存电荷。每一次换代都在问同一个问题:我们还能把信息塞进更小的空间,并更快地读出它吗?”


3.5 摩尔定律:预言了 60 年的黄金法则

化学家的意外预言

1965 年 4 月 19 日,《电子学》(Electronics)杂志刊登了一篇只有 4 页的文章,标题是《将更多元件塞进集成电路》(“Cramming More Components onto Integrated Circuits”)。文章的作者是戈登·摩尔(Gordon Moore),仙童半导体公司的研发总监。

摩尔在文中画了一条线:从 1959 年到 1965 年,单个集成电路上的晶体管数量从 1 个增加到了约 64 个。他预测:这个数字将每年翻一番,持续至少 10 年。

到 1975 年,摩尔将预测修订为每两年翻一番——这个节奏奇迹般地保持了近半个世纪。

摩尔后来承认,他当时"只是把几个数据点连成了一条线"——没有任何理论推导,只是一个经验观察。他甚至没有预料到这个预测后来会被称为"定律",并成为全球半导体产业的自我实现的预言

摩尔定律的三个时代

第一时代:几何缩放(Geometric Scaling, ~1970-2005)

也称为"Dennard 缩放"时代——以 DRAM 的发明者罗伯特·登纳德命名。在这个时期,晶体管的物理尺寸每代工艺缩小约 30%(面积缩小 50%),电压降低 30%,性能提升 40%,功耗降低 50%。真是一个"免费午餐"的时代:你可以同时获得更快的速度、更低的功耗和更小的面积。

第二时代:等效缩放(Equivalent Scaling, ~2005-2015)

登纳德缩放约在 2005 年终结——泄漏电流变得太大,不能再降低电压了。此后,工艺进步不再是"免费"的性能提升,而需要引入新材料和新结构:

  • 应变硅(Strained Silicon):90nm 节点,Intel 2003
  • 高 K/金属栅极(High-K/Metal Gate):45nm 节点,Intel 2007
  • FinFET(鳍式场效应晶体管):22nm 节点,Intel 2011

第三时代:3D 与异构集成(3D and Heterogeneous Integration, ~2015-至今)

当平面缩放接近物理极限(原子尺度),半导体产业转向了"第三个维度":

  • 3D NAND 堆叠(2013-,详见 3.4 节)
  • 2.5D 封装(硅中介层):AMD Fiji(2015)
  • 3D 封装(垂直堆叠芯片):Intel Foveros(2019)、AMD 3D V-Cache(2022)
  • 芯粒/小芯片(Chiplet):AMD Zen 2(2019)开始,将大芯片拆成多个小芯片
  • 背面供电(Backside Power Delivery):Intel PowerVia(2024),将电源布线从芯片正面移到背面

晶体管数量的指数增长

让我们用实际数据来看看"每两年翻一番"意味着什么:

年份处理器制程晶体管数量两年翻番速率
1971Intel 400410μm2,300
1974Intel 80806μm6,0002.6x(超速!)
1978Intel 80863μm29,000
1982Intel 802861.5μm134,000
1985Intel 803861.5μm275,000
1989Intel 804861μm1,200,0002.0x
1993Intel Pentium0.8μm3,100,000
1995Intel Pentium Pro0.6μm5,500,000
1997Intel Pentium II0.35μm7,500,0002.0x
1999Intel Pentium III0.25μm28,000,000
2000Intel Pentium 40.18μm42,000,0003.3x
2006Intel Core 2 Duo65nm291,000,000
2008Intel Core i7 (Nehalem)45nm731,000,0002.03x
2010Intel Core i7 (Westmere)32nm1,170,000,000
2011Intel Core i7 (Sandy Bridge)32nm1,160,000,000
2013Intel Core i7 (Haswell)22nm1,400,000,000
2015Intel Core i7 (Broadwell)14nm1,900,000,000
2017Intel Core i9 (Skylake-X)14nm7,200,000,000
2019AMD Ryzen 9 3950X7nm7,600,000,000
2020Apple M15nm16,000,000,000>2x
2022Apple M1 Ultra5nm114,000,000,000使用 UltraFusion 双芯互联
2023Apple M2 Ultra5nm134,000,000,000
2024NVIDIA H100 (GH100)4nm80,000,000,000单芯片,非拼接

从 1971 年到 2024 年,单个芯片上的晶体管数量从 2,300 增加到 80,000,000,000——增长了约 3 千 5 百万倍

如果汽车工业以同样的速度进步:1971 年的大众甲壳虫应该能在 2024 年:

  • 达到 1,050,000,000 公里/小时(接近光速的 97%)
  • 百公里油耗 0.000004 毫升
  • 售价 0.0007 美分

当然,汽车没有复制这个增长曲线,因为汽车是物理对象,而晶体管是信息对象——信息的复制和缩放服从完全不同的规律。

"摩尔定律已死"的 20 年争论

从 1990 年代开始,每隔几年就会有人宣布"摩尔定律已死"。以下是历史上几次著名的"讣告":

  • 1998 年:IBM 首席科学家声称"摩尔定律在 2005 年终结"——因为光刻技术的波长限制。结果:EUV(极紫外光刻)在 2018 年拯救了它
  • 2005 年:登纳德缩放终结——但 3D 和全新材料又延续了等效缩放
  • 2012 年:硅原子的直径是 0.2nm,“3nm 是物理极限”
  • 2020 年:台积电 5nm 量产,用 EUV 光刻——3nm 及以下将成为可能(用 GAA 环绕栅极代替 FinFET)
  • 2024 年:摩尔本人已于 2023 年去世。NVIDIA CEO 黄仁勋说"摩尔定律已死"并提出了"黄氏定律"

所以,摩尔定律究竟是死了,还是仍在以不同形式活着?

答案取决于你问的是哪一个"摩尔定律":

  • 晶体管数量每两年翻倍:已明显放缓(目前在 2.5-3 年翻倍)
  • 单位计算成本每两年减半:仍在持续——不是因为晶体管更小,而是因为架构创新(chiplet、3D 封装、专用加速器)
  • 每瓦性能每两年翻倍:基本停滞——这是整个行业最头疼的问题

黄仁勋的"黄氏定律":GPU 的加速世界

2018 年,NVIDIA CEO 黄仁勋在 GTC 大会上提出了一个"反摩尔定律":

“摩尔定律已死。从现在起,软件将决定性能,而非硬件。我们将用 GPU 加速一切。”

他认为,通用 CPU 的性能提升已经停滞(单核性能从 2015 年的 Skylake 到 2024 年几乎没有本质提升),但 GPU 通过架构专用化仍然保持着超摩尔定律的增长速度:

年份GPU晶体管数FP32 TFLOPS相比上代提升
2016GTX 1080 (Pascal)7.2B8.9
2018RTX 2080 Ti (Turing)18.6B13.41.5x
2020RTX 3090 (Ampere)28.3B35.62.7x
2022RTX 4090 (Ada Lovelace)76.3B82.62.3x
2024B200 (Blackwell)208B~180(估计)~2.2x

GPU 晶体管数量每 2 年约增长 2.5-3 倍——比摩尔定律更快。这是因为 GPU 可以用更多晶体管来做并行计算,而 CPU 受限于单核性能的天花板。

黄氏定律的核心观点:通用计算的增长速度已经饱和,未来的性能提升来自架构专用化——为特定问题设计专用硬件(AI 加速器、GPU、DPU、NPU 等)。

技术深入:从 10μm 到 3nm 的工艺演进

半导体工艺的名称"X 纳米"曾经指代晶体管栅极的物理长度。但在 2011 年之后,这个数字变成了营销术语,与任何实际物理尺寸没有直接关系。

工艺节点年份(量产)代表产品实际的栅极长度真实的物理最小间距
10μm1971Intel 400410,000 nm10,000 nm
6μm1974Intel 80806,000 nm6,000 nm
1.5μm1982Intel 802861,500 nm1,500 nm
0.8μm1993Intel Pentium800 nm800 nm
0.25μm1999Pentium III250 nm250 nm
90nm2003Pentium 4 “Prescott”~50 nm90 nm
45nm2007Core 2 “Penryn”~30 nm45 nm
22nm2011Core i “Ivy Bridge”~26 nm22 nm(FinFET 时代开始)
14nm2014Core i “Broadwell”~20 nm14 nm
10nm2019Core i “Ice Lake”~12 nm10 nm
7nm2019AMD Ryzen 3000 (TSMC)~6 nm7 nm
5nm2020Apple M1 (TSMC)~5 nm5 nm
3nm2023Apple A17 Pro (TSMC)~3 nm3 nm
2nm2025(预计)待定~2 nm2 nm(GAA 纳米片)

关键观察:22nm 以后,工艺名称已经脱离了任何物理特征长度。TSMC 的 5nm 并不比 Intel 的 10nm 小一半——它们在晶体管密度上大致相当。工艺命名成了各家公司的营销策略。

光刻技术:人类最精密的制造工艺

半导体制造的核心是光刻(Photolithography)——将电路图案"印"到硅晶圆上。这个过程类似于胶片摄影,但精度高了一百万倍。

光刻的基本原理

  1. 在硅晶圆上涂一层光刻胶(photoresist)——一种对光敏感的化学物质
  2. 通过掩模(photomask)照射特定波长的光——掩模上画着电路图案
  3. 光刻胶被光照射的部分发生化学变化——可以被显影液溶解(正胶)或不被溶解(负胶)
  4. 蚀刻掉暴露的硅(或沉积新材料),形成电路结构
  5. 清除剩余光刻胶,一个图案层完成

一个现代芯片(如 Apple M4)需要经过约 60-80 次光刻步骤,每次的精度要求都在纳米级——相当于在 1 毫米的宽度上画出 10 万条线。

光刻波长的演进

年代光源波长可实现的最小特征尺寸
1970s汞灯(g-line)436 nm约 3 μm
1980s汞灯(i-line)365 nm约 1 μm
1990sKrF 准分子激光248 nm约 0.25 μm(250 nm)
2000sArF 准分子激光193 nm约 65 nm(干式)
2005+浸没式 ArF193 nm(水中=134 nm有效)约 45 nm
2018+EUV(极紫外)13.5 nm约 7 nm → 3 nm

EUV 光刻——最疯狂的工程成就之一

ASML(荷兰公司)的 EUV 光刻机是迄今为止人类制造的最复杂的机器。一台 EUV 光刻机:

  • 重 180 吨(相当于 3 辆 M1 艾布拉姆斯坦克)
  • 包含 100,000 多个零件
  • 需要 40 个集装箱来运输
  • 售价约 3.8 亿美元(2024 年价格,最新 High-NA EUV 型号)
  • 年产量:约 50 台(ASML 是全球唯一供应商)

EUV 光的产生方式极其疯狂:在真空中滴下直径 30 微米的锡液滴,用 50,000 瓦的二氧化碳激光轰击液滴两次——第一次将液滴展平,第二次将其加热到 50 万摄氏度(比太阳表面热 80 倍),产生等离子体,发射出 13.5nm 的极紫外光。这个过程每秒重复 50,000 次。

为什么用 13.5nm? 因为更短的波长(如 X 射线)无法被镜头聚焦(所有材料对 X 射线都有折射率约等于 1),而更长的波长无法分辨 7nm 以下的特征。13.5nm 是物理上可用的"最软"X 射线。

反射镜:EUV 光无法使用透镜(所有材料都会吸收它),所以 EUV 光刻机使用多层钼/硅反射镜——每面镜子的反射率约 70%,经过 12 面镜子后,最终只有约 1% 的 EUV 光到达晶圆。这就是为什么 EUV 光源需要 250 瓦的初始功率——最终只有约 2.5 瓦的 EUV 光到达晶圆表面。

仅此一项技术,就占用了 ASML 超过 30 年的研发时间,投入了数百亿美元——而全球只有这一家公司掌握了它。

番外篇:摩尔的后半生

戈登·摩尔在 1968 年共同创立了英特尔("Integrated Electronics"的缩写),并担任 CEO 直到 1987 年,董事长直到 1997 年。他在英特尔推行一种极其平等主义的管理文化——没有高管私人车位,没有独立办公室,所有人都坐在开放的格子间里(包括他自己)。

2001 年,摩尔和妻子贝蒂创建了戈登与贝蒂·摩尔基金会。到 2023 年摩尔去世时,该基金会已捐赠超过 60 亿美元,支持科学、环境保护、患者护理和湾区的社区发展。摩尔基金会是加州理工学院的最大单一捐赠者(6 亿美元)和斯坦福大学绿色图书馆的主要资助者。

摩尔被广泛认为是"最不爱炫富的亿万富翁"。他住在同一所房子里超过 50 年,开一辆旧车,穿着格子衬衫出现在超市里。他的名言:“如果你有多余的钱,而世界有需要解决的问题,把这两者联系起来应该是世界上最简单的事。”

2023 年 3 月 24 日,戈登·摩尔在夏威夷家中安详去世,享年 94 岁。他看到了他的"定律"延续了 58 年——比他预测的"10 年"长了近 5 倍。

金句

“摩尔定律不是物理规律,而是一种社会契约——整个半导体产业集体承诺:我们每两年把性能翻一倍,把成本降一半。60 年来,这个承诺没有被打破。这不是因为容易,而是因为每次看起来不可能的时候,都有人找到了让不可能变成可能的方法。”


3.6 人生启示录

前面五节讲述的是技术,这一节我们谈谈这些技术背后的人——以及他们的人生给我们这些"改变世界的程序员"留下什么启示。

启示一:一个正确架构的力量——做好决策框架比做好每个决策更重要

冯·诺依曼的 EDVAC 报告只有 101 页,但它定义了一个仍在 80 年后使用的基本架构。他不是在解决一个具体问题——他是在定义一个问题可以被解决的框架

这就是架构师与工程师的区别:工程师在既定框架内做决策;架构师定义框架本身。 一个正确的框架比一千个正确的决策更有价值——因为正确的决策只能在正确的框架内做出。

想一想:如果你是一名程序员,你每天做的"架构决策"——选择什么技术栈、用什么设计模式、如何组织代码——其实就是你的"冯·诺依曼报告"。它会影响你和你的团队未来数年的工作效率。所以,在做出这些决策时,值得投入更多时间去思考:这个框架足够通用吗?有什么是被它排除在外的?它是在对付当前的几个问题,还是在定义未来问题的解决空间?

“做正确的事比正确地做事更重要。定义什么是’正确的事’需要一个好框架。”

启示二:时机就是一切——先行者未必是胜利者

ENIAC 用的是十进制,EDVAC 用了二进制。从数学和工程角度看,二进制有无数优势:逻辑电路更简单(开/关两种状态)、抗干扰能力强、与布尔代数天然匹配。但 ENIAC 的十进制先入为主,团队花费了大量精力为十进制辩护。

这不是一个孤立的故事。在技术史上,后发优势(second-mover advantage)比先发优势更常见:

  • Betamax 比 VHS 画质更好,但 VHS 赢得了录像带战争
  • Macintosh 比 Windows 更早拥有图形界面,但 Windows 赢得了 PC 市场
  • Netscape 发明了浏览器商业模式,但 Internet Explorer(后来的 Chrome)赢了浏览器战争

冯·诺依曼架构也是如此:哈佛 Mark I(1944)在时间上先于 EDVAC 报告(1945),但冯·诺依曼架构统治了世界。为什么?

因为冯·诺依曼架构提供的不是"最好的性能",而是"最大的通用性"。哈佛架构在某些场景下更快、更安全,但它的分离存储设计让编写软件变得更复杂——而软件产业正在蓬勃发展。冯·诺依曼选择了"让程序员的生活更简单",而这个选择被证明比"让硬件更快"更有市场价值。

“不要做第一个,要做一个做得最好的。有时’最好’不意味着技术最优,而意味着最方便、最通用、最能适应变化。”

启示三:指数增长的魔法——持续微小进步胜过偶尔的巨大突破

摩尔定律的本质不是"每两年翻倍"这个数字,而是持续进步的力量

想象两个团队:A 团队每年有 50% 的概率实现 100% 的性能提升,50% 的概率原地踏步;B 团队每年稳定实现 35% 的性能提升。10 年后谁的性能更好?

  • A 团队的期望:5 次成功 x 2x = 32x 提升
  • B 团队的确定性:(1.35)^10 = 20.1x 提升

但如果看实际概率分布:B 团队确定有 20x 的提升,而 A 团队有 20% 以上的概率只获得 8x 甚至更少的提升。

摩尔定律的 60 年告诉我们:持续、可靠、可预测的进步比偶尔的突破更强大。 每年提升 35%(远远小于摩尔定律的 100%),保持 60 年,累积起来就是 6,700 万倍的增长。

这对程序员意味着什么?

  • 每天学习一点点。如果每天进步 1%,一年后是 37.8 倍——虽然实际不可能那么快,但方向是对的
  • 代码每天重构一点点。不需要一次重写整个系统——每次改一个模块,保持代码持续改善
  • 能力圈慢慢扩张。不用急着学所有新技术——先精通一门语言,然后每次扩展一个领域

“复利是世界第八大奇迹——这句爱因斯坦的名言适用于投资,也适用于技术。”

启示四:抽象的价值——"存储程序"概念如此简单,却催生了软件产业

冯·诺依曼的"存储程序"概念,在今天看来几乎是 trivial 的:把程序编码成数字,和数据放在同一个存储器里。如此简单的想法,凭什么改变了世界?

因为它创造了一个抽象层(abstraction layer)。

在"存储程序"之前,"写程序"意味着物理上连接真空管、拨动开关、穿纸带。每一次"编程"都是与硬件搏斗。冯·诺依曼说:No——程序是一种信息,信息可以被编码、存储、复制、修改和传输。这个简单的概念飞跃将编程从物理世界拉入了符号世界。

一旦承认"程序是信息",下面的推论就自动成立:

  • 程序可以被编译器自动生成(从高级语言到机器码)
  • 程序可以被操作系统管理(加载、调度、终止)
  • 程序可以被网络传输(下载、远程执行)
  • 程序可以被版本控制(diff、merge、回滚)
  • 程序可以被多个程序员协作开发(模块化、接口、API)
  • 程序可以在运行时修改自己(JIT 编译、动态加载、热更新)

没有这一层抽象,以上任何一项都是不可能的。而这一切来自一个洞察:程序和数据的本质都是信息。

“伟大的抽象不是复杂的——它是当你听到时觉得’这不是很明显吗’的东西。它的伟大之处在于,在它出现之前,没有人这么想过。”

启示五:跨界者的降维打击——冯·诺依曼的数学背景让他看到了工程师看不到的东西

ENIAC 的莫奇利和埃克特是纯粹的工程师。他们眼中的计算机是一堆真空管、电阻和电容——他们关心的是:如何用更少的元件做更快的加法?如何让真空管更可靠?如何降低功耗和散热?

冯·诺依曼是数学家。他眼中的计算机是:一个能够执行任意符号变换的通用机器。 他关心的是:这个机器的逻辑结构是什么?它能不能模拟图灵机?它的指令集是否完备?它能不能计算所有可计算函数?

从工程角度看,用二进制还是十进制无所谓——只要能用硬件实现就行。但从数学角度看,二进制与布尔代数(George Boole, 1847)天然对应,而任何二进制函数都可以用与非门(NAND)表示——这保证了逻辑完备性。十进制没有这种保证。

从工程角度看,程序是否存储在内存中无所谓——只要计算结果正确。但从数学角度看(哥德尔不完备定理, 1931):任何足够强大的形式系统都可以编码为数字。所以程序可以编码为数字——这是一个数学必然性,不是工程便利性。

这就是跨界的降维打击:工程师看到的是具体的约束和优化,数学家看到的是结构的本质和逻辑的边界。冯·诺依曼没有发明新的硬件——他提供了理解硬件的新的概念框架

“不要只做一个语言专家、框架专家或工具专家。学一些数学、物理、经济学——这些学科给了你俯瞰技术领域的’直升机视角’。当你从一个不同的领域看过来时,工程师花十年解决的问题可能只是你教科书上的一个习题。”

启示六:简单性的力量——为什么复杂的东西会失败,简单的东西会胜出

冯·诺依曼架构之所以能统治 80 年,核心原因不是它"最优"——而是它"最简单"。哈佛架构在某些方面是更好的设计(更快、更安全),但它更复杂。在工程中,简单性是一种压倒性的竞争优势

让我们用数据说话:

简单架构复杂架构结果
冯·诺依曼(统一存储)哈佛(分离存储)冯·诺依曼统治通用计算
RISC(精简指令集)CISC(复杂指令集)RISC 赢了移动端,但 x86 通过"内部 RISC"在桌面端存活
TCP/IP(简单但不可靠)OSI 七层模型(完美但复杂)TCP/IP 统治互联网
JSON(简单)XML(功能丰富)JSON 统治 Web API
Git(分布式)SVN/CVS(集中式)Git 统治版本控制
Markdown(纯文本)WYSIWYG(所见即所得)Markdown 统治技术写作

规律:简单的东西未必能赢,但复杂的东西一定会输。 简单的架构让更多人能理解、使用、修改和改进它——而庞大的生态社区才是持久的护城河。

启示七:合作与争议的辩证——你不知道你欠谁的

冯·诺依曼的 EDVAC 报告只有他一个人的署名,但许多关键思想来自团队。这引发了一个至今仍在计算机科学界回荡的问题:个人天才与团队协作,谁更重要?

答案可能是:两者都需要,但角色不同。 莫奇利和埃克特提供了工程直觉和实践经验,冯·诺依曼提供了数学抽象和逻辑架构。没有前者,后者是空中楼阁;没有后者,前者是没头苍蝇。

但为什么历史上只有冯·诺依曼被记住?因为抽象者比建造者更容易被记住。抽象是"公式",一旦被发现就永远成立;建造是"实现",总会被更好的实现取代。ENIAC 的真空管电路早已被淘汰,但"存储程序"的概念仍在每一台计算机中运行。

这个教训对今天的程序员尤为深刻:不要只做"代码的建造者",还要做"思想的抽象者"。 写代码只是实现,但设计好的接口、定义好的抽象、制定好的规范——这些才是真正持久的东西。

启示八:技术债务——冯·诺依曼架构也是一笔债

在赞美冯·诺依曼 80 年之后,我们也必须诚实地说:冯·诺依曼架构本身也是一笔巨大的技术债务。

冯·诺依曼瓶颈——CPU 和内存之间那根"吸管"——每年浪费了全球数据中心的数百万兆瓦时的电力。CPU 花费 50% 以上的时间在等待数据从内存传输过来,这些等待时间消耗的电力被白白浪费了。

这不是冯·诺依曼的错——1945 年没有人能预见到晶体管会变得如此快,而导线传输延迟却无法同比缩小。但这是一个提醒:每一个伟大的架构决策,在足够长的时间尺度上,都会变成技术债务。 你今天做的"最佳实践",在 20 年后可能成为下一代程序员口中的"历史包袱"。

所以,在拥抱任何架构或范式时,请保持一份谦逊:它只是当前约束条件下的最优解,不是永恒的真理。 计算机科学仍然是一门年轻的学科——我们甚至还没有过完第一个百年。未来还有无数种组织计算的方式等待被发现。

第三章小结:我们能从冯·诺依曼那里带走什么?

  1. 一个好的架构值 80 年。你的代码今天写的架构决策,可能在 80 个月后还在运行——值得多花时间思考
  2. 二进制战胜了十进制,不是因为’更好’,而是因为’更通用’。在选择技术方案时,优先考虑通用性和可扩展性
  3. "程序即数据"是最伟大的抽象之一。在你的日常工作中,是否有类似的"元认知"可以创造新的可能性?
  4. 存储器是瓶颈,但缓存层次结构是工程智慧的典范。"用空间换时间"是计算机科学最深刻的 tradeoff
  5. 摩尔定律快结束了,但人类对计算能力的需求永远不会结束。下一个范式——量子计算、神经形态计算、光子计算——正在实验室中萌芽
  6. 跨界思维是最高级别的竞争力。冯·诺依曼不是工程师,而是数学家——但正是他的数学洞察定义了现代计算机

下一章,我们将见证存储程序的第一个伟大应用——编译器——的诞生。格蕾丝·霍珀——一个 37 岁的数学系副教授——将告诉世界:程序员不需要写机器码,他们需要的是一种叫做"编程语言"的东西。


章末金句:“冯·诺依曼给计算机装上了灵魂。在此之前,计算机是计算器;在此之后,计算机是可编程的通用机器。存储程序不是一种电路设计,而是一种世界观——它告诉我们:信息就是信息,无论是数据还是代码。”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员光剑

光子AI,让AI照亮每个人。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值