后端硬核:CPU 到底是怎么执行程序的?磁盘真的比内存慢几万倍吗?

作为后端开发者,你每天写代码、调接口、查数据库,但你有没有想过——你敲下的那一行 a = 1 + 2,CPU 到底是怎么把它跑起来的?

这篇文章从图灵机讲到冯·诺依曼,从寄存器讲到机械硬盘,把计算机最底层的运行逻辑一次讲透。

CPU 与存储器


2.1 CPU 是如何执行程序的?

一切从图灵机开始

要理解 CPU 怎么执行程序,得先回到 1936 年。

那一年,阿兰·图灵提出了一个假想的机器——图灵机(Turing Machine)。它不是一台真实存在的机器,而是一个思想实验:

┌─────────────────────────────────────────────┐
│  无限长纸带 → 分成一个个格子,每个格子写一个符号  │
│  读写头 → 在纸带上左右移动,读格子、写格子      │
│  状态寄存器 → 记录当前机器状态                 │
│  规则表 → 告诉机器:当前状态+读到什么符号时,     │
│          该写什么、往哪走、切换到什么状态         │
└─────────────────────────────────────────────┘

图灵的结论惊人地简洁:任何可计算的问题,都可以通过这台简单的机器一步步完成。 这就是后来所有计算机的理论原型。

你现在用的这台电脑,本质上就是一台极其复杂、速度极快的图灵机——只不过纸带换成了内存,读写头换成了 CPU,规则表换成了程序指令。


冯·诺依曼模型:现代计算机的蓝图

1945 年,冯·诺依曼在 EDVAC 报告中提出了一个核心设计思想:

数据和指令,都存放在同一个存储器里。

这就是著名的 冯·诺依曼架构(Von Neumann Architecture)。在此之前,早期计算机要么靠接线板编程,要么指令和数据分开存放,灵活性极差。

冯·诺依曼模型由五大部件组成:

冯诺依曼架构

部件作用类比
输入设备把外部信息送进计算机键盘、鼠标、网卡
输出设备把计算机处理结果送出来显示器、打印机、硬盘
存储器(Memory)存放指令和数据既放"菜谱",也放"食材"
运算器(ALU)执行算术和逻辑运算厨房的灶台和菜刀
控制器(CU)指挥协调所有部件按节拍工作厨房里的大厨

其中,运算器 + 控制器 = 中央处理器(CPU)。


内存:既放指令,也放数据

在冯·诺依曼模型里,内存是一个巨大的"房间阵列":

地址        内容
─────────────────────
0x1000     ┌─────────┐
0x1001     │  指令   │  ← 程序代码放在这里
0x1002     │  指令   │
0x1003     │  指令   │
0x1004     ├─────────┤
0x1005     │  数据   │  ← 变量、常量放在这里
0x1006     │  数据   │
0x1007     └─────────┘

每个内存单元有一个唯一编号(地址),每个单元存放一个固定位数的二进制数据(通常是 8 位 = 1 字节)。CPU 要读某个数据,就把地址扔到地址总线上,内存就把对应单元的数据送到数据总线上。

关键洞察:CPU 根本分不清内存里存的是指令还是数据——它只是根据当前位置(程序计数器 PC)去取,然后机械地执行。 这就是为什么缓冲区溢出等漏洞能得逞:你输入的数据,被 CPU 当成了指令来执行。


CPU:大脑的内部构造

拆开一颗现代 CPU,你会看到里面有这些关键部件:

┌──────────────────── CPU 内部 ────────────────────┐
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌────────────────┐  │
│  │ 程序计数器 │  │ 指令寄存器 │  │ 通用寄存器组    │  │
│  │   (PC)   │  │   (IR)   │  │ R0 R1 R2 ...   │  │
│  └──────────┘  └──────────┘  └────────────────┘  │
│                                                  │
│  ┌──────────────────────────────────────────┐     │
│  │           控制单元 (CU)                  │     │
│  │  译码 → 产生控制信号 → 指挥全机           │     │
│  └──────────────────────────────────────────┘     │
│                                                  │
│  ┌──────────────────────────────────────────┐     │
│  │        算术逻辑单元 (ALU)               │     │
│  │  加法 / 减法 / 与 / 或 / 异或 / 比较      │     │
│  └──────────────────────────────────────────┘     │
│                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────┐  │
│  │  L1 Cache   │  │  L2 Cache   │  │ 内存管理 │  │
│  │  (数据/指令) │  │             │  │ 单元MMU │  │
│  └─────────────┘  └─────────────┘  └─────────┘  │
└──────────────────────────────────────────────────┘

几个核心寄存器的职责:

  • 程序计数器(PC):存着"下一条指令的内存地址"。CPU 每取完一条指令,PC 自动加 1(或跳到目标地址),就像书签一样告诉你读到哪了。
  • 指令寄存器(IR):当前正在执行的指令,临时存在这里。
  • 通用寄存器:CPU 内部最快的"小内存",用来临时存放运算数据。x86-64 有 16 个通用寄存器,ARM64 有 31 个。

总线:连接一切的信息高速公路

CPU、内存、外设之间靠 总线(Bus) 通信。总线分三组:

                    地址总线 (Address Bus)
                  ←———————————————————————
              CPU 告诉内存:"我要读哪个地址"
                  
                    数据总线 (Data Bus)
                  ⇅⇅⇅⇅⇅⇅⇅⇅⇅⇅⇅⇅⇅⇅⇅
              CPU 和内存之间搬运数据
                  
                    控制总线 (Control Bus)
                  ———————————————————————→
              CPU 发出读/写信号
  • 地址总线:宽度决定了 CPU 能访问多大的内存空间。32 位地址总线 = 2³² = 4GB 寻址空间;64 位地址总线理论上能寻址 16EB(尽管实际用不到这么多)。
  • 数据总线:宽度决定了 CPU 一次能搬多少数据。32 位数据总线 = 一次传 4 字节。
  • 控制总线:传读/写、中断、时钟等控制信号。

线路位宽与 CPU 位宽

我们常说的"32 位 CPU"“64 位 CPU”,到底指什么?

CPU 的"位",指的是 ALU 一次能处理的二进制数据位数。

对比项32 位 CPU64 位 CPU
通用寄存器宽度32 位64 位
一次算术运算最大2³² ≈ 42 亿2⁶⁴ ≈ 1844 亿亿
虚拟地址空间最多 4GB理论 16EB,实际常用 48 位 = 256TB
数据总线(典型)32 位64 位

注意:位宽 ≠ 总线宽度。 比如 64 位 CPU 可能用 256 位甚至 512 位的数据总线(SIMD 指令),也可能地址总线只有 48 位(实际寻址)。位宽描述的是"计算能力",总线宽度描述的是"搬运能力"。


程序执行的基本过程:取指 → 译码 → 执行

不管多复杂的程序,CPU 周而复始地做这三件事(取指周期):

         ┌──────────┐
         │   取指    │  PC → 内存地址总线 → 指令经数据总线 → IR
         │  Fetch   │  PC = PC + 1
         └────┬─────┘
              ↓
         ┌──────────┐
         │   译码    │  CU 分析 IR 中的 opcode,确定要做什么
         │  Decode │  并准备好操作数来源
         └────┬─────┘
              ↓
         ┌──────────┐
         │   执行    │  ALU 执行运算,或读写内存,或跳转到新地址
         │  Execute │  结果写回寄存器或内存
         └────┬─────┘
              ↓
         回到取指,无限循环

这个循环叫做 指令周期(Instruction Cycle),也叫 Fetch-Decode-Execute 循环。现代 CPU 每秒要跑几十亿次这个循环。


实战拆解:a = 1 + 2 到底经历了什么?

假设我们写了一行代码:

int a = 1 + 2;

这行代码编译成汇编后,大概长这样(简化版):

MOV  R1, #1      ; 把立即数 1 存入寄存器 R1
MOV  R2, #2      ; 把立即数 2 存入寄存器 R2
ADD  R3, R1, R2  ; R1 + R2,结果存入 R3
MOV  [0x2000], R3  ; 把 R3 的值写入内存地址 0x2000(变量 a)

我们逐条追踪 CPU 的执行过程:

第一条指令 MOV R1, #1
阶段发生了什么
取指PC=0x1000,CPU 把 0x1000 送到地址总线,内存返回指令 MOV R1, #1,存入 IR。PC 变为 0x1004
译码CU 解读 opcode 为"MOV",知道要把立即数存入寄存器
执行立即数 1 被送入 R1
第二条指令 MOV R2, #2

同理,PC=0x1004,取指 → 译码 → 执行,R2 = 2。PC 变为 0x1008。

第三条指令 ADD R3, R1, R2
阶段发生了什么
取指PC=0x1008,取回 ADD 指令
译码CU 识别出这是加法指令,准备 ALU
执行ALU 接收 R1(1) 和 R2(2),执行加法,得到 3。结果写入 R3
第四条指令 MOV [0x2000], R3
阶段发生了什么
取指PC=0x100C,取回指令
译码CU 识别出这是"写内存"操作
执行把地址 0x2000 送到地址总线,把 R3 的值 3 送到数据总线,发出写信号。内存把 3 写入 0x2000 单元

至此,a = 1 + 2 执行完毕。你写代码时感觉瞬间完成的事,CPU 实际上经历了 4 次取指、4 次译码、4 次执行,中间还穿插了无数次总线传输和寄存器读写。

指令执行流程


指令:CPU 听得懂的"外语"

一条指令在内存中就是一串二进制数,通常由两部分组成:

┌──────────────┬──────────────────────┐
│    Opcode    │      Operand(s)      │
│   操作码      │       操作数          │
│  (做什么)   │    (对谁做)         │
└──────────────┴──────────────────────┘
   比如 ADD         比如 R1, R2

比如 ADD R1, R2 翻译成二进制(假设):

00000001  00000001  00000010
  ADD        R1       R2

指令的类型

CPU 的指令集(Instruction Set)虽多,但大致可以归为五大类:

类型作用典型指令生活类比
数据传送在寄存器和内存之间搬数据MOV, PUSH, POP, LOAD, STORE搬箱子
算术逻辑做计算ADD, SUB, MUL, DIV, AND, OR, XOR, CMP算账
控制流改变执行顺序JMP, BEQ, BNE, CALL, RET转弯/跳站
移位移位操作SHL, SHR, ROL, ROR左移右移
特殊中断、特权指令INT, IRET, HLT, IN, OUT按门铃/睡觉

你写的所有高级语言代码,最终都被编译器翻译成这五类指令的组合。


指令的执行速度:为什么 CPU 越来越快?

一条指令需要多少时间?这取决于几个关键指标:

  • 时钟频率(Clock Rate):CPU 每秒振荡多少次。比如 3 GHz = 每秒 30 亿次时钟周期,一个周期 = 0.33 纳秒。
  • CPI(Cycles Per Instruction):平均每条指令需要几个时钟周期。
  • IPC(Instructions Per Cycle):每个周期能执行几条指令,是 CPI 的倒数。

理论执行时间 = 指令数 × CPI / 时钟频率

那为什么 CPU 频率从 1980 年代的 1 MHz 飙到了今天的 5 GHz+,却越来越难往上提?

  1. 物理极限:频率越高,发热越严重(Intel 曾经因为功耗墙,"Tick-Tock"模式被迫放缓)。
  2. 架构优化:现代 CPU 不靠堆频率,而是靠 流水线(Pipeline)、超标量(Superscalar)、乱序执行(Out-of-Order)、分支预测 等技术,让一个周期内能完成更多指令。

流水线的思想很简单:就像工厂流水线,取指、译码、执行不是等一条指令完全做完才开始下一条,而是像流水线一样重叠起来——当第 1 条在执行时,第 2 条在译码,第 3 条在取指。这样吞吐率就大大提高了。

非流水线:
指令1: [取指][译码][执行]
指令2:        [取指][译码][执行]
指令3:               [取指][译码][执行]

流水线:
周期:  1   2   3   4   5   6
指令1: 取指 译码 执行
指令2:      取指 译码 执行
指令3:          取指 译码 执行

理想情况下,流水线让每 1 个周期就能完成一条指令(IPC=1),超标量设计更进一步,能做到 IPC > 1。


2.1 小结

  • 图灵机定义了"可计算"的理论边界,是所有计算机的思想原型。
  • 冯·诺依曼模型把指令和数据统一存放在内存中,CPU 通过 取指→译码→执行 的循环来运行程序。
  • CPU 内部有 PC、IR、ALU、寄存器组 等关键部件,通过 地址总线、数据总线、控制总线 与外界通信。
  • 你写的每一行代码,最终都被拆解成 数据传送、算术逻辑、控制流 等基本指令,在 CPU 上一条一条地执行。
  • 现代 CPU 靠 流水线、超标量、缓存 等技术提升性能,而不是单纯堆频率。

2.2 磁盘比内存慢几万倍?

存储器的层次结构:为什么不能全都用最快的?

如果内存速度跟寄存器一样快、容量跟硬盘一样大、价格像白菜一样便宜,那该多好?

但物理世界不允许——速度越快的存储,必然容量越小、价格越贵。于是计算机科学家设计了一套 金字塔式的存储层次结构:

存储层次金字塔

从上到下,速度越来越慢、容量越来越大、价格越来越便宜。


第一层:寄存器(Register)

  • 位置:CPU 内部,是 ALU 旁边的小存储单元
  • 容量:极小,x86-64 约 16 个通用寄存器(×8 字节 = 128 字节)
  • 速度:1 个时钟周期,约 0.3 ns
  • 价格:极贵(和 CPU 晶圆一起制造,面积成本极高)
  • 作用:CPU 运算时的临时"草稿纸",没有寄存器,ALU 根本没法工作

打个比方:寄存器就是你手里的笔和眼前的草稿纸,随手就能写。


第二层:CPU Cache(高速缓存)

CPU Cache 是为了缓解 CPU 和内存之间的速度差距而设计的。它分为多级:

缓存容量(典型)延迟位置
L1 Cache32 KB(数据)+ 32 KB(指令)~1 ns(4 周期)CPU 核心内部
L2 Cache256 KB ~ 1 MB~3 ns(12 周期)CPU 核心内部
L3 Cache8 MB ~ 64 MB(所有核心共享)~10 ns(40 周期)CPU 内部

为什么需要缓存?因为程序有 局部性原理:

  • 时间局部性:刚访问过的数据,很快可能再访问
  • 空间局部性:刚访问过的数据旁边的数据,很快可能被访问

Cache 就是利用这个原理,把 CPU 常用的数据提前从内存"搬"到更快的地方。命中率越高,CPU 等待内存的时间越少。


第三层:内存(RAM / Main Memory)

  • 位置:主板上的内存条,DDR4/DDR5
  • 容量:8 GB ~ 64 GB(消费级),服务器可达 TB 级
  • 速度:~60 ~ 100 ns(约 200 ~ 300 个 CPU 周期)
  • 价格:约 ¥ 30 ~ 60 / GB
  • 作用:存放正在运行的程序和数据

内存就像你的书桌:你正在用的东西放在桌上,方便随时拿,但桌面空间有限。

这里有个关键的对比:

CPU 寄存器访问一次:~0.3 ns
内存访问一次:     ~80 ns

差距 ≈ 250 倍!

也就是说,CPU 每访问一次内存,相当于等了 250 个寄存器周期。这就是为什么 Cache 如此重要——它在寄存器和内存之间充当了"中间缓冲"。


第四层:SSD(固态硬盘)

  • 位置:主板上的 M.2 插槽或 SATA 接口
  • 容量:256 GB ~ 4 TB
  • 速度:~100 μs(微秒)= 100,000 ns(随机读取)
  • 顺序读取:~3000 ~ 7000 MB/s(NVMe PCIe 4.0)
  • 价格:约 ¥ 0.3 ~ 0.6 / GB

SSD 没有机械结构,用闪存芯片存储数据,速度比 HDD 快一个数量级以上。


第五层:HDD(机械硬盘)

  • 位置:SATA 或 SAS 接口
  • 容量:1 TB ~ 20 TB
  • 速度:~10 ms(毫秒)= 10,000,000 ns(随机寻道)
  • 顺序读取:~100 ~ 200 MB/s
  • 价格:约 ¥ 0.05 ~ 0.1 / GB

HDD 靠磁盘旋转和磁头摆动来读写,寻道时间(磁头移动到目标磁道) 是最大的开销。


存储器层次关系:一张表看懂

层级类型典型容量访问延迟相对速度(对比内存)价格(每 GB)
1寄存器< 1 KB0.3 ns~250 倍快极其昂贵
2L1 Cache32 KB1 ns~80 倍快极贵
3L2 Cache256 KB ~ 1 MB3 ns~25 倍快很贵
4L3 Cache8 ~ 64 MB10 ns~8 倍快贵
5内存 RAM8 ~ 64 GB80 ns基准(1 倍)~¥ 40
6SSD256 GB ~ 4 TB100,000 ns~1,000 ~ 1,500 倍慢~¥ 0.5
7HDD1 ~ 20 TB10,000,000 ns~100,000 ~ 150,000 倍慢~¥ 0.07

磁盘比内存慢几万倍?——这个说法对不对?

我们来算一笔账:

内存随机访问:~80 ns
SSD 随机访问:~100,000 ns → 是内存的 ~1,250 倍
HDD 随机访问:~10,000,000 ns → 是内存的 ~125,000 倍

所以:

  • SSD 比内存慢约 1,000 ~ 1,500 倍——不是"几万倍"
  • HDD 比内存慢约 10 万 ~ 15 万倍——这才是真正的"几万倍甚至几十万倍"

这个说法在 机械硬盘(HDD) 的语境下基本成立,但在 固态硬盘(SSD) 的语境下,差距只有约 1000 倍,远没有"几万倍"那么夸张。

不过这里有个陷阱:以上是随机访问的延迟对比。如果看顺序读写带宽,差距会小很多:

存储类型顺序读取带宽
DDR5-4800 内存~38,400 MB/s
NVMe SSD (PCIe 4.0)~7,000 MB/s
SATA SSD~550 MB/s
HDD~200 MB/s

顺序读写时,内存只比最快的 SSD 快约 5 倍,比 HDD 快约 200 倍。真正的鸿沟在随机访问,而不是顺序读写。

这也是为什么数据库、操作系统、编程语言都在拼命做 预取(Prefetch) 和 顺序 I/O 优化——能顺序读就不随机读,能一次多读就不一次次读。


价格和性能的博弈:为什么不把缓存做得更大?

每 GB 成本大致对比(数量级):

寄存器:   几百万美元 / GB
L1 Cache: 几十万美元 / GB
L2 Cache: 几万美元 / GB
L3 Cache: 几千美元 / GB
DDR5 内存: ~40 美元 / GB
NVMe SSD:  ~0.10 美元 / GB
HDD:      ~0.01 美元 / GB

从 L3 Cache 到内存,单位容量成本下降了约 100 倍;从内存到 SSD,又下降了约 400 倍;从 SSD 到 HDD,再下降约 10 倍。

这就是为什么 Cache 只能做几 MB 到几十 MB,而内存能做到几十 GB,硬盘能做到几十 TB——用容量换价格,用速度换成本,这就是存储层次的核心逻辑。


对后端开发的启示

理解了存储层次,你就能理解很多后端工程实践背后的道理:

  1. 为什么 Redis 快? 因为它把数据放在内存里,比 MySQL(磁盘)快上万倍。
  2. 为什么要做数据库索引? 因为索引减少了磁盘随机 I/O,把"全表扫描"变成了"定位查找"。
  3. 为什么 CPU 缓存行(Cache Line)是 64 字节? 因为空间局部性——每次取 64 字节进 L1,相邻数据大概率马上会用到。
  4. 为什么锁竞争会导致性能暴跌? 因为 Cache 一致性协议(MESI)会让多核之间反复同步缓存行,比普通访问慢得多。
  5. 为什么 Rust 强调"零拷贝"? 因为每多一次内存拷贝,就是一次 80 ns 的延迟——在每秒处理百万级请求时,这些加起来就是巨大的开销。

2.2 小结

  • 计算机存储器是一个 金字塔层次结构:寄存器 → Cache → 内存 → SSD → HDD,越往上越快、越小、越贵。
  • 内存随机访问约 80 ns,SSD 约 100 μs(10 万 ns),HDD 约 10 ms(1000 万 ns)。
  • "磁盘比内存慢几万倍"这个说法,对 HDD 成立(约 10 万倍),对 SSD 不成立(约 1000 倍)。
  • 真正的性能鸿沟在 随机访问延迟,顺序带宽的差距小得多。
  • 理解存储层次,是理解数据库、缓存、操作系统、并发编程等后端核心问题的基石。

写在最后

从图灵的一条纸带,到冯·诺依曼的五大部件,从 CPU 内部的寄存器到机房里的机械硬盘——计算机的世界看似复杂,但拆开来看,不过是 “取指→译码→执行” 的无限循环,和 “快的小、慢的大” 的存储阶梯。

你写的每一行代码、发的每一个请求、查的每一条数据库记录,都在这个层次结构上上下下穿梭。理解了这些,你就不只是在"调 API",而是真正理解了机器是怎么干活的。


关注我,下期我们聊聊 操作系统如何调度进程、虚拟内存到底是怎么回事、中断和系统调用的底层原理——这些才是后端工程师真正的"内功心法"。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐