一、前置知识:ARM / Thumb / Thumb-2 指令位宽
为避免地址错乱,先明确三种指令集字节宽度(考试必考):
ARM 指令集:固定 32bit(4字节)
Thumb 基础指令集:固定 16bit(2字节),精简指令
Thumb-2 指令集(Cortex-M4 专用):兼容 16bit/32bit,功能最强,带DSP指令
本文所有汇编示例,全部为Thumb-2 16bit(2字节)基础指令。
所以:每取完一条指令,PC 自动 +2。
二、PC 硬件绝对真理(全文唯一标准)
摒弃所有模糊话术,本文统一使用 Cortex-M 硬件真实规则:
PC 的值 = 当前时钟周期正在取指的指令地址
通俗理解:
CPU 此刻 IF 阶段正在读哪条指令,PC 里面存的就是谁的地址。
三、Cortex-M4 三级流水线核心规则
三级流水线并行工作:取指(IF) → 译码(ID) → 执行(EX)
关键硬性规则(杜绝所有错误):
每个时钟周期,IF 只能取 1 条指令,绝对不会同时取两条
同一时刻,流水线三段同时干活,处理三条不同指令
指令从 IF→ID→EX 逐级向后移动,每周期前进一格
四、为什么读 PC 会 +4?
测试代码(全部2字节指令)
0x08000100 MOV R0, PC ; I1 0x08000102 MOV R1, #1 ; I2 0x08000104 ADD R0, R0, R1 ; I3逐时钟周期流水线真值表
时钟周期 | 取指 IF(正在取指) | 译码 ID | 执行 EX | PC 值(当前正在取指地址) |
|---|---|---|---|---|
1 | I1 | 空 | 空 | 0x08000100 |
2 | I2 | I1 | 空 | 0x08000102 |
3 | I3 | I2 | I1 | 0x08000104 |
核心原理(彻底看懂 +4)
在周期3这一刻:
EX 正在执行 I1(MOV R0,PC),自身地址 0x08000100
PC 此时此刻正在取指 I3,PC = 0x08000104
差值:0x08000104 − 0x08000100 =4
✅ 最终结果:R0 = 0x08000104
本质:流水线超前预取,不是BUG,是硬件机制。
五、修改 PC 跳转原理
跳转测试代码(全部2字节指令)
Main LDR PC, =Label1 ; IA 修改PC,实现跳转 MOV R0, #5 ; IB 被冲刷,不执行 MOV R1, #6 ; IC 被冲刷,不执行 Label1 MOV R2, #10 ; ID 正常执行 MOV R3, #20 ; IE 正常执行跳转瞬间流水线状态表(重点!必考)
流水线阶段 | 当前缓存指令 | 硬件处理结果 | 最终运行结果 |
|---|---|---|---|
EX 执行 | IA(LDR PC,=Label1) | 完整执行完毕,PC 被改成 Label1 地址 | 跳转生效 |
ID 译码 | IB(MOV R0,#5) | 流水线冲刷,直接丢弃 | R0 不等于 5 |
IF 取指 | IC(MOV R1,#6) | 流水线冲刷,直接丢弃 | R1 不等于 6 |
跳转后流水线重建(逐周期单指令取指)
跳转后 PC = Label1 地址,重新开始逐条取指:
时钟周期 | IF 取指(单条) | ID 译码 | EX 执行 | PC 当前取指地址 | 结果 |
|---|---|---|---|---|---|
T1 | ID | 空 | 空 | Label1 | 预取ID |
T2 | IE | ID | 空 | Label1+2 | ID译码 |
T3 | 后续指令 | IE | ID | Label1+4 | R2=10 |
T4 | 后续指令 | 后续 | IE | Label1+6 | R3=20 |
✅ 最终运行结果:R2=10,R3=20,中间两句完全不执行。
六、中断场景流水线规则
流水线阶段 | 处理规则 |
|---|---|
EX 执行 | 当前指令必须执行完毕 |
ID 译码 | 流水线冲刷,直接丢弃 |
IF 取指 | 流水线冲刷,直接丢弃 |
中断本质:硬件自动改写 PC,冲刷流水线,造成中断延时。
七、终极总结
Cortex-M4 使用 Thumb-2 指令,基础指令 2 字节,取指一次 PC+2。
PC 唯一真值:当前时钟周期正在取指的指令地址。
三级流水线并行运行,每周期 IF 只取一条指令,逐级流动。
MOV R0,PC 读出 +4:因为指令执行时,PC 已经预取到后两条的地址。
跳转本质:修改 PC;改 PC 必冲刷流水线,预取未执行指令全部作废。
跳转、中断都会冲刷流水线,产生额外时钟开销。