Menu Close

CPU 如何执行一条指令:Fetch、Decode、Execute 全流程解析

每一个程序,最终都会变成一系列由 CPU 执行的指令。

CPU 如何执行一条指令:Fetch、Decode、Execute 全流程解析

例如,一行简单的 C 语言代码:

c = a + b;

对程序员来说非常直观。

但是,处理器并不能直接理解 C 语言。

程序经过编译之后,会变成机器指令,例如:

add $t0, $t1, $t2

或者其他处理器架构中的等效指令。

接下来,CPU 必须按照一套非常有组织的流程来执行这些指令。

从高层来看,这个过程通常被概括为:

取指(Fetch)→ 译码(Decode)→ 执行(Execute)

但现代 CPU 实际执行的工作,要比这三个词复杂得多。

CPU 需要从内存中取得指令,判断这条指令是什么意思,读取所需要的操作数,执行运算,在必要的时候访问内存,然后保存计算结果。

理解这个过程,是理解“软件如何最终变成真实硬件活动”的最好方式之一。

1. 程序是一系列指令

当一个程序运行时,它的机器代码会被存放在内存中。

CPU 所看到的程序,本质上就是一系列二进制机器指令。

从概念上看,内存中可能存放着:

Instruction 1

Instruction 2

Instruction 3

Instruction 4

每一条指令都会告诉处理器执行某一种操作。

常见的操作包括:

  • 两个数相加
  • 数字相减
  • 从内存加载数据
  • 把数据写入内存
  • 比较两个数值
  • 跳转到另一条指令
  • 调用一个函数

处理器不断读取这些指令,然后逐条执行。

这个不断重复的过程,被称为 指令周期(Instruction Cycle)

2. 程序计数器告诉 CPU 去哪里找指令

CPU 必须知道下一条应该执行的指令在哪里。

为此,处理器内部有一个特殊的寄存器,通常称为 程序计数器(Program Counter),简称 PC

程序计数器中保存的是下一条指令的地址。

例如:

PC = 0x1000

表示下一条指令位于内存地址:

0x1000

处理器随后会取得这个地址中的指令。

执行之后,程序计数器通常会继续移动到下一条指令的位置。

从概念上看:

PC → 指令内存 → 指令

如果没有程序计数器,CPU 就不知道自己当前运行到了程序中的什么位置,也不知道接下来应该执行哪条指令。

3. 第一阶段:取指 Fetch

第一个主要步骤是 取指阶段(Fetch Stage)

CPU 将程序计数器中保存的地址发送给内存系统。

内存系统随后把这个地址对应的指令返回给处理器。

在一个简化的系统中,可以表示为:

程序计数器 → 内存 → 指令

例如,假设程序计数器中保存的是:

0x1000

而内存地址 0x1000 中保存了一条 ADD 指令。

CPU 就会把这条指令读取出来。

在现代处理器中,这条指令通常并不是直接从主内存 RAM 中取得,而更可能来自 指令缓存(Instruction Cache)

但是基本原理仍然相同:

CPU 根据一个地址取得下一条需要执行的指令。

4. 指令进入 CPU

指令被取得之后,CPU 必须暂时保存这条指令,以便进一步判断它究竟表示什么。

在一些简单的处理器结构图中,这条指令可能被放入一个叫做 指令寄存器(Instruction Register) 的部件中。

现代 CPU 的内部结构通常更加复杂,但基本原理是一样的。

此时,处理器内部已经拿到了这条二进制指令。

例如,这个二进制模式可能代表:

ADD R1, R2, R3

它的含义是:

读取寄存器 R2 中的值。

读取寄存器 R3 中的值。

把两个值相加。

然后把结果保存到寄存器 R1 中。

但是,此时 CPU 仍然需要某些电路来识别这些二进制位究竟代表什么意思。

这就是下一步的任务:

译码。

5. 第二阶段:译码 Decode

第二个主要阶段是 译码(Decode)

一条机器指令内部包含多个字段,用来告诉处理器应该执行什么操作,以及应该使用哪些操作数

根据不同的指令集,这些字段可能包括:

  • 操作类型
  • 源寄存器
  • 目标寄存器
  • 立即数
  • 内存寻址信息

其中,用来表示操作类型的字段通常称为:

操作码(Opcode)

对于一条 ADD 指令来说,操作码告诉 CPU

需要执行加法。

其他操作码还可能表示:

  • SUB
  • AND
  • OR
  • LOAD
  • STORE
  • BRANCH
  • JUMP

译码器会检查指令中的这些二进制位,然后判断接下来需要启动哪些硬件。

6. 控制单元协调整个操作

CPU 并不是简单地把整条指令直接送进 ALU。

实际上,指令经过译码之后,会产生一系列 控制信号(Control Signals)

这些控制信号负责协调处理器内部不同部件的工作。

控制逻辑可能需要告诉 CPU

  • 应该读取哪些寄存器
  • ALU 应该执行什么运算
  • 是否需要读取内存
  • 是否需要写入内存
  • 是否应该把结果写回寄存器
  • 下一条指令应该从哪里取得

例如,一条 ADD 指令可能产生类似下面这样的控制行为:

读取寄存器 R2

读取寄存器 R3

告诉 ALU 执行加法

把结果写入 R1

因此,控制单元实际上就像整个 数据通路(Datapath) 的协调者。

7. CPU 读取源寄存器

很多指令所使用的数据,已经存放在 CPU 的寄存器中。

假设处理器执行:

ADD R1, R2, R3

CPU 必须取得 R2 和 R3 中保存的数值。

假设寄存器文件中:

R2 = 10

R3 = 20

寄存器文件会把这两个数值发送到执行单元。

从概念上看:

R2 → ALU 输入 A

R3 → ALU 输入 B

机器指令中的寄存器名称,实际上是在告诉 CPU 应该读取寄存器文件中的哪些位置。

真正进入 ALU 参与运算的,并不是“R2”或者“R3”这些名字,而是这些寄存器中保存的实际数值。

8. 第三阶段:执行 Execute

现在,CPU 进入 执行阶段(Execute Stage)

对于算术和逻辑指令来说,执行阶段通常会使用 ALU。

ALU 的全称是:

Arithmetic Logic Unit

中文通常称为:

算术逻辑单元

ALU 可以执行很多操作,例如:

  • 加法
  • 减法
  • AND
  • OR
  • 比较
  • 移位

继续使用前面的例子:

R2 = 10

R3 = 20

ALU 接收到这两个数值。

控制单元告诉 ALU:

执行加法。

于是 ALU 计算:

10 + 20 = 30

此时,结果已经在 CPU 内部产生了。

但是,这条指令通常还没有真正结束。

因为这个结果还需要保存到某个地方。

9. 并不是每条指令都以相同方式使用 ALU

所谓的“执行阶段”,并不意味着 CPU 每次都只是进行普通的算术运算。

不同类型的机器指令,会以不同方式使用数据通路。

例如,一条算术指令可能让 ALU 计算:

A + B

一条比较指令可能让 ALU 判断:

A < B

而一条内存访问指令,则可能利用 ALU 来计算一个内存地址。

例如,一条 LOAD 指令可能表示:

从地址 R2 + 8 的位置加载数据。

此时 ALU 可能首先计算:

address = R2 + 8

然后 CPU 再使用这个地址去访问内存。

因此,即使某些指令表面上看起来并不是“算术指令”,它们仍然可能需要使用 ALU。

10. LOAD 和 STORE 指令的内存访问

算术指令通常主要处理寄存器中的数据。

但是,程序中的大量数据实际上存放在内存中。

这时,LOADSTORE 指令就非常重要。

LOAD 指令负责把数据从内存复制到寄存器:

Memory → Register

也就是:

内存 → 寄存器

而 STORE 指令负责把数据从寄存器写入内存:

Register → Memory

也就是:

寄存器 → 内存

例如:

LOAD R1, 8(R2)

可能表示:

首先计算 R2 中的地址再加上 8。

然后读取这个内存地址中的数据。

最后把读取到的数据存入 R1。

因此,CPU 内部可能经历这样的数据流:

寄存器 → ALU → 地址 → 内存 → 寄存器

这也是为什么执行一条机器指令,绝不仅仅只是使用 ALU 做一次计算。

11. 写回阶段保存结果

操作完成之后,计算结果通常还需要返回到寄存器文件。

这个阶段通常称为:

写回(Write Back)

继续使用前面的 ADD 例子:

R2 = 10

R3 = 20

ALU 得到:

30

然后 CPU 执行:

R1 = 30

因此,整个过程大约可以表示为:

指令

译码

读取 R2 和 R3

ALU

结果

写入 R1

到了这里,这条 ADD 指令才算真正执行完成。

新的数值 30 已经保存在 R1 中,可以继续被后面的指令使用。

12. 程序计数器移动到下一条指令

在执行当前指令的同时,处理器还必须确定:

下一条指令在哪里?

在正常情况下,程序计数器会按照指令顺序向前移动。

如果当前指令位于某个地址,那么下一条指令通常位于紧接着的下一个指令地址。

但是,当 CPU 遇到 控制流指令(Control Flow Instruction) 时,情况就会发生变化。

常见的控制流指令包括:

  • 分支
  • 跳转
  • 函数调用
  • 函数返回

例如,一条分支指令可能告诉处理器:

如果两个数相等,就从另一个地址继续执行。

这时,程序计数器的值就会被修改。

程序中的循环、if 语句、函数调用以及其他控制结构,最终在机器层面都依赖类似的机制来实现。

13. Fetch、Decode、Execute 实际上是一个更大的周期

我们经常使用:

Fetch → Decode → Execute

来描述 CPU 的指令执行过程。

这种表达非常有用,因为它抓住了指令执行最核心的概念。

但是,一个更加完整的简化指令周期通常应该表示为:

Fetch

Decode

Read Registers

Execute

Memory Access

Write Back

也就是:

取指

译码

读取寄存器

执行

访问内存

写回

不同类型的指令,并不一定会使用其中的所有阶段。

例如,ADD 指令可能根本不需要访问数据内存。

STORE 指令通常不会把计算结果重新写入一个通用寄存器。

而分支指令可能主要修改程序计数器。

CPU 中的数据通路是一套共享硬件。

不同的机器指令决定:

这一次究竟需要使用其中哪些部分。

14. 一条 MIPS ADD 指令如何穿过整个 CPU

现在来看一条典型的 MIPS 风格指令:

add $t0, $t1, $t2

它的含义是:

$t0 = $t1 + $t2

现在我们已经可以完整跟踪这条指令在 CPU 内部的执行过程了。

首先,程序计数器确定这条指令所在的地址。

CPU 取得这条机器指令。

译码器识别出:

这是一条 ADD 指令。

指令中还指定:

$t1

和:

$t2

是两个源寄存器。

寄存器文件同时读取这两个寄存器中的值。

控制单元告诉 ALU:

执行加法。

ALU 将两个数值相加。

计算结果通过写回数据通路返回。

最后,结果被保存到:

$t0

因此,在汇编语言中看起来只有短短一行:

add $t0, $t1, $t2

实际上却会启动 CPU 内部多个硬件部件:

Program Counter

Instruction Memory

Decoder

Control Logic

Register File

ALU

Write-Back Path

Register File

也就是:

程序计数器

指令内存

译码器

控制逻辑

寄存器文件

ALU

写回通路

寄存器文件

这才是一条简单汇编指令背后真正的硬件含义。

15. 现代 CPU 执行指令的速度更快

基本的指令周期模型,对于理解处理器非常重要。

但是,真正的现代 CPU 要复杂得多。

现代 CPU 通常不会等待一条指令完全执行结束之后,才开始处理下一条指令。

现代处理器可能使用:

  • 流水线(Pipelining)
  • 多个执行单元
  • 分支预测
  • 高速缓存
  • 超标量执行(Superscalar Execution)
  • 乱序执行(Out-of-Order Execution)
  • 寄存器重命名(Register Renaming)

以流水线为例,多条指令可以同时处于不同的执行阶段。

例如:

一条指令正在执行。

另一条指令正在译码。

还有一条指令可能已经开始取指。

因此,简单的:

Fetch → Decode → Execute

并不能完整描述现代高性能 CPU 的内部工作方式。

但是,它仍然是理解现代 CPU 的基础。

因为流水线、超标量、分支预测、乱序执行等更复杂的技术,本质上都在尝试让这些基本的指令处理步骤执行得更快、更高效。

结语

CPU 并不是把整个程序当成一个巨大的操作一次性执行。

CPU 执行的是:

一条又一条机器指令。

每一条指令都会经历一系列有组织的硬件活动。

程序计数器确定指令的位置。

CPU 取得指令。

译码器判断指令是什么意思。

控制单元启动所需要的数据通路。

寄存器提供操作数

ALU 执行算术或逻辑操作。

必要的时候,CPU 会访问内存。

最后,计算结果可能被重新写入寄存器。

然后 CPU 继续执行下一条指令。

因此,我们熟悉的:

Fetch → Decode → Execute

也就是:

取指 → 译码 → 执行

代表了计算机内部最基本、也最重要的工作过程之一。

一条简单的汇编指令:

add $t0, $t1, $t2

从程序员的角度看,似乎只是一次简单的加法。

但是在处理器内部,它实际上会形成一条协调的数据流,依次经过:

程序计数器、指令内存、译码器、控制逻辑、寄存器文件、ALU 和写回电路。

理解指令周期,可以帮助我们建立一座非常重要的桥梁:

从软件,一直理解到硬件。

接下来,一个很自然的问题就是:

指令和数据究竟来自哪里?CPU 又是如何与内存通信的?

这就直接引出了下一个主题:

CPU 如何与内存通信(How the CPU Talks to Memory)。

Posted in 计算机结构教程