2. 复位向量与入口点:ARM的异常向量表 vs RISC-V的复位向量
好,咱们直接切入正题。芯片上电或复位后,CPU第一件事是干啥?说白了,就是找到第一条指令在哪执行。这个位置,就是复位向量。ARM和RISC-V在这块的设计思路,嗯,差别还挺大的。
2.1 ARM的异常向量表:一张固定的跳转地图
ARM架构,尤其是Cortex-M系列之前的经典ARM核(比如Cortex-A/R),用的是异常向量表。这张表通常放在内存的起始地址,比如0x00000000,或者通过VBAR寄存器重定位。
这张表长什么样?我直接给你看代码:
; ARM 异常向量表(经典布局)
0x00000000: LDR PC, Reset_Addr ; 复位
0x00000004: LDR PC, Undef_Addr ; 未定义指令
0x00000008: LDR PC, SWI_Addr ; 软件中断
0x0000000C: LDR PC, Prefetch_Addr ; 预取中止
0x00000010: LDR PC, Data_Abort_Addr ; 数据中止
0x00000014: LDR PC, IRQ_Addr ; 外部中断
0x00000018: LDR PC, FIQ_Addr ; 快速中断
每个表项占4个字节,存放的是一条跳转指令。CPU一旦检测到异常,硬件会自动跳转到对应的表项去执行。我个人习惯把这张表理解成「硬件级的switch-case语句」。
关键点:ARM的异常向量表是固定大小、固定位置的。每个异常都有自己专属的槽位,不能乱放。
我在项目中遇到过一个问题:某次调试时,发现IRQ中断死活不响应。查了半天,原来是Bootloader把向量表重定位到了RAM,但中断控制器还在用默认地址找表。嗯,这种「表与硬件不同步」的坑,我踩过不止一次。
2.2 RISC-V的复位向量:灵活到让你选择困难
RISC-V这边就完全不一样了。它没有「异常向量表」这个概念,而是用mtvec(Machine Trap-Vector Base-Address Register)寄存器来指定入口地址。
复位向量呢?它由硬件设计者决定,通常是一个固定的地址,比如0x80000000。但RISC-V规范并没有强制要求,你完全可以把它放在0x00000000,或者0x20000000,甚至0xFFFFFFFF——只要你的硬件支持。
# RISC-V 复位向量示例(常见配置)
# 假设复位地址为 0x80000000
.section .text.reset
.globl _start
_start:
li sp, 0x80010000 # 设置栈指针
jal main # 跳转到主函数
你想想看,RISC-V的复位向量其实就是一条「起点指令」。它不像ARM那样有一整张表,而是只有一个入口点。剩下的异常处理呢?全靠软件自己搭。
我的建议:如果你刚开始接触RISC-V,别被它的灵活性吓到。复位向量通常由芯片厂商在硬件设计时固定,你只需要在链接脚本里把启动代码放到那个地址就行。
2.3 核心差异对比:一张表 vs 一个点
咱们用表格来对比一下,这样更直观:
| 特性 | ARM(Cortex-A/R) | RISC-V |
|---|---|---|
| 复位入口 | 异常向量表第0项 | 由硬件定义的固定地址 |
| 异常处理 | 硬件自动查表跳转 | 软件通过mtvec寄存器指定 |
| 表大小 | 固定32字节(8项×4字节) | 无固定表,由软件实现 |
| 灵活性 | 低,表结构固定 | 高,可自定义异常分发 |
| 典型地址 | 0x00000000 或 0xFFFF0000 | 0x80000000(常见) |
说白了,ARM帮你把异常处理的「框架」搭好了,你只需要往里填代码。RISC-V则把「框架」的搭建权交给了你——你可以自己决定怎么处理异常,甚至可以用一套完全不同的机制。
2.4 避坑指南:我曾经踩过的两个坑
坑一:ARM向量表重定位后忘记同步
我曾经在移植U-Boot时,把向量表从ROM搬到了RAM,但忘了更新VBAR寄存器。结果一触发中断,CPU还是去ROM里找表,直接跑飞。嗯,这个bug我调了整整一个下午。
坑二:RISC-V的mtvec对齐要求
RISC-V的mtvec寄存器要求地址按4字节对齐(如果使用直接模式),或者按128字节对齐(如果使用向量模式)。我刚开始写RISC-V启动代码时,随手把mtvec设成了一个未对齐的地址,结果异常处理直接崩溃。后来翻手册才发现这个细节。
注意:RISC-V的mtvec寄存器有两个模式:Direct(直接模式)和Vectored(向量模式)。Direct模式下所有异常都跳转到同一个入口,由软件分发;Vectored模式下每个中断有独立的入口。别搞混了。
2.5 实际项目中的选择
我个人在实际项目中,ARM和RISC-V都做过。ARM的异常向量表适合「快速上手、稳定可靠」的场景,尤其是裸机或RTOS。RISC-V的灵活性则更适合「需要深度定制」的场景,比如你想实现一个非标准的异常处理流程。
举个例子:我在一个RISC-V项目里,把所有的异常都统一路由到一个函数,然后在函数里用C语言做分发。这样写起来更灵活,调试也方便。但ARM上你就不能这么干——硬件强制你走向量表,除非你修改异常处理逻辑。
好了,这一节就到这里。下一节咱们聊聊启动流程的第二步:栈指针初始化。别小看这一步,栈设不对,后面全白费。