ESC
输入关键词搜索文章标题和内容

从晶体管到main():CPU到底怎么执行0和1?

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

从晶体管到main():CPU到底怎么执行0和1?

从MOS管开关到五级流水线,从上电复位到跳转main(),拆解CPU执行二进制指令的完整链路。

一、0和1的物理真相:晶体管开关

CPU看到的不是0和1,是电压。NMOS管栅极加高电平(如3.3V)导通,低电平(0V)截止;PMOS管相反。一个NMOS+一个PMOS组成CMOS反相器——输入1输出0,输入0输出1,这就是非门。

用晶体管串并联,搭出与门、或门、异或门。异或门+与门组合成半加器,两个半加器+或门组成全加器,全加器级联就是加法器——ALU的核心。所有运算,归根结底是晶体管开关的通断组合。

层级 构成 功能
晶体管 NMOS + PMOS 开关通断,表示0/1
逻辑门 非门、与门、或门、异或门 基本逻辑运算
加法器 全加器级联 二进制加法
ALU 加法器+逻辑运算单元 算术和逻辑运算
CPU ALU+控制器+寄存器+缓存 完整指令执行

二、指令周期:五级流水线

经典RISC处理器(如ARM Cortex-M、RISC-V)把一条指令的执行拆成五个阶段:

flowchart TB A["取指 IF<br/>PC→地址总线→指令存入IR"] --> B["译码 ID<br/>解析操作码+操作数<br/>读取寄存器"] B --> C["执行 EX<br/>ALU运算/地址计算"] C --> D["访存 MEM<br/>Load/Store数据"] D --> E["写回 WB<br/>结果写入寄存器"] E -.->|"PC递增<br/>下一条指令"| A style A fill:#bbdefb,stroke:#1565c0,color:#0d47a1 style B fill:#c8e6c9,stroke:#2e7d32,color:#1b5e20 style C fill:#fff9c4,stroke:#f57f17,color:#e65100 style D fill:#e1bee7,stroke:#7b1fa2,color:#4a148c style E fill:#ffcdd2,stroke:#c62828,color:#b71c1c

1. 取指(IF — Instruction Fetch)

PC寄存器保存下一条指令的地址。CPU把PC值送上地址总线,从存储器读出指令,存入IR(指令寄存器)。随后PC自动递增,指向下一条指令。ARM Thumb指令集下PC加2,ARM指令集下加4,RISC-V 32位加4。

2. 译码(ID — Instruction Decode)

IR中的二进制码送入译码器。以RISC-V的R型指令为例,32位被切分为:

[31:25] funct7 | [24:20] rs2 | [19:15] rs1 | [14:12] funct3 | [11:7] rd | [6:0] opcode

译码器根据opcode判断指令类型(ADD/LW/SW/BEQ...),从寄存器组读出源操作数,生成控制信号。

3. 执行(EX — Execute)

控制单元根据译码结果调度:

  • 算术逻辑指令 → ALU执行运算
  • 访存指令 → ALU计算内存地址
  • 分支指令 → ALU比较+计算跳转目标

4. 访存(MEM — Memory Access)

仅Load/Store指令需要此阶段:

  • Load:从内存读数据到寄存器
  • Store:把寄存器数据写入内存

运算类指令跳过此阶段。

5. 写回(WB — Write Back)

ALU运算结果或内存读取的数据写回目标寄存器,同时更新状态标志位(N/Z/C/V)。

流水线的意义:当第1条指令在执行阶段时,第2条在译码,第3条在取指——三条指令重叠执行,吞吐量提升近5倍。

三、从上电到main():ARM Cortex-M启动全流程

以STM32(Cortex-M3/M4)为例,按下复位键后硬件自动完成以下操作:

来自 linuxros.cn · linuxROS
flowchart TB classDef hw fill:#e3f2fd,stroke:#1976d2,color:#1565c0 classDef init fill:#fff8e1,stroke:#f57c00,color:#e65100 classDef sys fill:#e8f5e9,stroke:#388e3c,color:#1b5e20 classDef user fill:#f3e5f5,stroke:#7b1fa2,color:#4a148c subgraph HW["硬件自动完成"] direction TB A(["上电复位"]):::hw --> B["读取0x00000000<br/>→ MSP初始值"]:::hw B --> C["读取0x00000004<br/>→ Reset_Handler地址"]:::hw C --> D["PC跳转到Reset_Handler"]:::hw end subgraph SW["软件初始化"] direction TB D --> E["复制.data段<br/>Flash → RAM"]:::init E --> F["清零.bss段"]:::init end subgraph SYS["系统初始化"] direction TB F --> G["SystemInit()<br/>配置时钟HSE/PLL"]:::sys G --> H["__libc_init_array()<br/>C++构造函数"]:::sys end H --> I(["跳转main()"]):::user

第1步:读取向量表

Cortex-M复位后,CPU从地址0x00000000读取4字节写入MSP(主栈指针),再从0x00000004读取4字节作为Reset_Handler的入口地址写入PC。

注意:STM32的Flash物理起始地址是0x08000000,芯片通过地址别名机制将0x00000000映射到Flash起始位置。Cortex-M7还支持通过VTOR寄存器重定位向量表。

向量表前几项:

偏移 内容 说明
0x00 初始MSP值 栈顶地址,必须8字节对齐
0x04 Reset_Handler地址 最低位必须为1(Thumb模式)
0x08 NMI_Handler 不可屏蔽中断
0x0C HardFault_Handler 硬件故障

第2步:Reset_Handler执行初始化

Reset_Handler是启动文件(如startup_stm32f103.s)中定义的汇编函数,依次完成:

  1. 复制.data段——有初值的全局变量从Flash搬到RAM
  2. 清零.bss段——未初始化的全局变量在RAM中填0
  3. 调用SystemInit()——配置系统时钟(HSE/HSI/PLL)
  4. 调用__libc_init_array()——C++构造函数
  5. 跳转main()
// 典型Reset_Handler伪代码(参考STM32 HAL库启动文件)
void Reset_Handler(void) {
    // 1. .data段:Flash → RAM
    uint32_t *src = &_sidata;  // Flash中的数据起始
    uint32_t *dst = &_sdata;   // RAM中的.data起始
    while (dst < &_edata) *dst++ = *src++;

    // 2. .bss段清零
    dst = &_sbss;
    while (dst < &_ebss) *dst++ = 0;

    // 3. 系统初始化
    SystemInit();
    __libc_init_array();

    // 4. 跳转用户代码
    main();
    while(1);  // main不应返回
}

四、指令执行实例:ADD指令的五级流水

以ARM指令ADD R0, R1, R2(R0 = R1 + R2)为例,假设R1=5, R2=3:

阶段 操作 数据流
IF PC指向指令地址,从Flash读取32位指令码 0xE0810002 → IR
ID 译码识别为ADD,读取R1和R2 R1=5, R2=3 → ALU输入
EX ALU执行加法 5 + 3 = 8
MEM 无内存操作,跳过 —
WB 结果8写入R0,更新标志位 R0=8, Z=0, C=0

五、常见问题

Q:为什么.data段要从Flash复制到RAM?

A:.data段的全局变量在运行时会被修改,Flash只读,必须复制到RAM才能写入。.bss段只需清零,初始值在Flash中不占空间。

Q:为什么向量表里存的是地址而不是跳转指令?

A:Cortex-M的向量表存的是中断服务程序的入口地址,不是指令。CPU硬件自动把该地址加载到PC,比执行跳转指令少一个时钟周期,中断响应更快。

Q:程序能直接在Flash上运行吗?

A:能。这叫XIP(Execute In Place),Cortex-M支持直接从Flash取指。但Flash读取速度比RAM慢(典型70ns vs 10ns),且变量必须在RAM中修改,所以.data/.bss段必须搬到RAM。

六、动手验证:GitHub上的CPU模拟器项目

想亲手实现一个CPU?这些开源项目值得参考:

项目 架构 语言 特点
RISCV-Simulator RISC-V RV64I C++ 五级流水线+分支预测
exactstep RISC-V/MIPS/ARM-v6m C++ 多架构指令精确模拟
6502-emulator MOS 6502 C++ 代码可读性优先,适合入门
sixty502 MOS 6502 Go 周期精确,含反汇编器

这些模拟器的核心都是同一个循环:cpu::step() → 取指 → 译码 → 执行 → 写回,和真实CPU的工作方式一一对应。

七、总结

CPU执行程序的本质链路:晶体管开关→逻辑门→加法器→ALU→五级流水线。从上电复位开始,CPU通过向量表找到栈指针和Reset_Handler地址,完成.data/.bss段初始化后跳转main()。理解这条链路,调试启动失败、HardFault、中断不响应等问题才有方向。

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页