2. RISC-V架构基础
RISC-V 是一套开放、模块化、可扩展的指令集架构(ISA)。它不像 x86 那样背负历史包袱,也不像 ARM 那样需要授权才能实现,而是以“基础指令集 + 可选扩展”的方式组织。对于多核 Linux 系统开发而言,理解 RISC-V 的指令集特点、寄存器组织、特权级、内存模型与多核扩展,是后续编写启动代码、移植内核、调试并发问题的基础。
一、指令集特点:模块化与规整编码
RISC-V 的核心思想是“小基础、大扩展”。基础整数指令集只有 RV32I / RV64I,指令长度固定为 32 位,编码规整,译码简单。常用扩展包括:
- M:整数乘除法;
- A:原子操作,多核同步的关键;
- F/D:单/双精度浮点;
- C:压缩指令,降低代码体积;
- V:向量扩展,用于高性能计算;
- H:虚拟化扩展,支持 Hypervisor。
Linux 通常要求 RV64GC,即 RV64I + M + A + F + D + C。这种模块化让同一套软件栈可以运行在从 MCU 到服务器级处理器的不同实现上。规整的编码也意味着流水线设计更简单,但代价是代码密度需要靠 C 扩展弥补。
二、寄存器组织:32 个通用寄存器与调用约定
RISC-V 定义 32 个通用寄存器 x0~x31,其中 x0 恒为 0。寄存器没有隐式含义,但 ABI 为其规定了角色:a0~a7 传参,a0/a1 返回结果,sp 栈指针,ra 返回地址,s0~s11 被调用者保存,t0~t6 调用者保存。这种约定直接影响函数调用、中断处理和上下文切换。
寄存器 ABI名 用途
x0 zero 恒为0
x1 ra 返回地址
x2 sp 栈指针
x10-x11 a0-a1 参数/返回值
x12-x17 a2-a7 参数
x8 s0/fp 保存寄存器/帧指针
x18-x27 s2-s11 被调用者保存
x5-x7 t0-t2 临时寄存器
x28-x31 t3-t6 临时寄存器
在 Linux 内核中,进程切换必须保存和恢复这些寄存器;中断入口也要根据 ABI 判断哪些寄存器需要压栈。理解寄存器角色,才能看懂 entry.S 和 switch_to 的汇编逻辑。
三、特权级:M/S/U 三级与 Trap 机制
RISC-V 定义三个特权级:M(Machine)、S(Supervisor)、U(User)。M 级最高,负责启动、时钟中断和底层控制;S 级运行 Linux 内核;U 级运行用户进程。Linux 通常运行在 S 级,通过 SBI 调用 M 级固件完成关机、IPI 等操作。
特权级切换由 Trap 机制完成。发生异常、中断或 ecall 时,CPU 保存 mepc/sepc,跳转到 mtvec/stvec 指定的入口,并根据 mcause/scause 判断原因。返回时执行 mret/sret。这套机制是系统调用、缺页异常和中断处理的基础。
stvec 是否设置、S 级中断是否在 sie 中使能,以及 sstatus.SIE 是否打开。四、内存模型与多核扩展
RISC-V 采用弱内存模型(RVWMO),允许硬件在不破坏单核语义的前提下重排访存。多核环境下,必须使用 fence 指令或 A 扩展的原子指令(lr/sc、amoswap)建立顺序。Linux 中的 smp_mb()、smp_rmb() 最终会落到这些指令上。
多核扩展方面,RISC-V 通过 CLINT/PLIC 或 ACLINT/AIA 管理中断,通过 IPI 实现核间中断。每个 hart 有独立的寄存器上下文,但共享内存和外设。启动时通常由一个 hart 执行初始化,再通过 sbi_send_ipi 唤醒其他 hart。
五、案例:从启动到多核并行的关键路径
以一颗 4 核 RV64GC 处理器为例。上电后 hart0 进入 M 级,执行 BootROM,初始化 CLINT 和内存,然后跳转到 S 级内核入口。内核在 S 级完成页表、中断控制器和调度器初始化后,通过 SBI 向 hart1~hart3 发送 IPI。其他 hart 收到 IPI 后从 sbi_hart_start 指定的地址开始执行,进入 smp_callin,最终加入调度。整个过程涉及特权级切换、寄存器上下文建立、fence 保证初始化顺序,以及 IPI 唤醒,是 RISC-V 多核 Linux 启动的典型路径。
六、常见误区与复盘
| 误区 | 正确理解 |
|---|---|
| 认为 RISC-V 只有一种指令集 | 基础 RV32I/RV64I 加多种扩展,Linux 常用 RV64GC |
| 忽略 A 扩展和 fence | 多核同步必须依赖原子指令和内存屏障 |
| 把 M 级和 S 级混为一谈 | Linux 运行在 S 级,M 级由固件管理,通过 SBI 交互 |
| 认为寄存器可以随意使用 | ABI 约定影响函数调用、中断和上下文切换 |
| 启动时只初始化一个 hart | 需通过 IPI 唤醒其他 hart,并保证内存顺序 |