RISCV汇编指令
参考
技术规范 Specifications
- Volume 1, Unprivileged Specification version 20240411 (非特权指令规范)
The RISC-V Instruction Set Manual Volume I Unprivileged Architecture Version 20240411 - Volume 2, Privileged Specification version 20240411 (特权指令规范)
The RISC-V Instruction Set Manual: Volume II Privileged Architecture Version 20240411
旧版规范 (20191213, 汪辰老师所用版本)
RISC-V Technical Specifications Archive
参考手册
一个可以将 C/C++/Python 的代码转换成 Riscv 汇编的工具网站
Compiler Explorer
汇编程序基本构成
一个完整的 RISC-V 汇编程序有多条 “语句” (statement) 组成, 一条典型的 RISC-V 汇编 “语句” 由3部分组成:
1 | [label:] [operation] [comment] |
其中方括号表示可选项 (支持空行).
label: GNU 汇编中, 任何以冒号结尾的标识符都被认为是一个标号. label 可以理解为给地址起了一个别名, 方便后面的代码通过 label 引用该地址.
-
文件标签在程序文件中是全局可见的, 所以定义不可重复. 文本标签通常被作为分支或跳转指令的目标地址, 如:
1
2
3loop: # 定义一个loop标签
...
j loop # 跳转到loop标签处 -
数字标签属于一种局部标签, 可重复定义, 通常用 0~9 之间的数字定义. 在被引用时, 数字标签通常需要带上字母 “f” 或 “b” 字母后缀, 表示 forward 向前搜索和 backward 向后搜索
1
2
3
4
5j 1f # 向前寻找并跳转至第一个数字为 1 的标签处
...
1: # 数字标签1
...
j 1b # 向后寻找并跳转至第一个数字为 1 的标签处
operation: operation 有以下几种类型
-
instruction (指令): 直接对应二进制机器指令的"真实"指令
-
pseudo-instruction (伪指令): 为了提高编写代码的效率, 通过伪指令来封装一条或者多条 instruction 来实现一些功能
-
directive (指示/伪操作): 通过类似指令的形式 (以 “.” 开头), 通知汇编器如何控制代码的产生等, 不对应具体的指令, 是写给汇编器看的指令.
-
macro (宏): 采用 .macro /.endm 自定义的宏
1
2
3.macro LOAD_IMM reg, imm
li \reg, \imm
.endm使用宏相比于函数有自己独特的优势. 例如函数调用会导致在函数体内 ra (return address) 寄存器, sp (stack point) 寄存器, 以及 a0 等参数寄存器的值发生变化, 这样不能访问到函数调用前寄存器的原始状态. 而宏不会, 宏只是单纯的文本替换, 不涉及到寄存器的变换.
comment: 汇编语言中采用 #
, ;
等方式进行行注释
1 | .macro do_nothing # directive |
Vscode 中 RISC-V Support 插件支持 RISC-V 语法的高亮显示
汇编文件的后缀 .s 和 .S 有大小写的区分. 其中小写 s 后缀表示纯的汇编语言, 不包含头文件和宏等预处理指令; 大写的 S 后缀表示包含头文件和宏等预处理指令的汇编文件.
寄存器
- RV32I 通用寄存器组包含 32 个通用寄存器 x0 ~ x31 和 1 个 pc 寄存器.
- 在 RISC-V 中, Hart在执行算术逻辑运算时所操作的数据必须直接来自寄存器.
- Hart 可以执行在寄存器和内存之间的数据读写操作, 读写操作使用字节 (Byte) 为基本单位进行寻址;
- 32 位的寄存器可以访问最多 2^32 个字节的内存空间, 2^32 bytes = 4 x (2^10)^3 bytes = 4 G bytes.
大端序与小端序
字节序 (Endianness), 又称端序或尾序, 指多字节数据在内存中的字节存放顺序
大端序 (Big-Endian): 数据的高位字节存放在内存的低地址 (先存高位字节)
小端序 (Little-Endian): 数据的低位字节存放在内存的低地址 (先存低位字节)
1 | decimal: 287454020 |
常用汇编指令
算术运算指令
Instruction | Name | FMT | Description © |
---|---|---|---|
add rd, rs1, rs2 | ADD | R | rd = rs1 + rs2 |
sub rd, rs1, rs2 | SUB | R | rd = rs1 - rs2 |
addi rd, rs1, imm | ADD Immediate | I | rd = rs1 + imm |
lui rd, imm | Load Upper Imm | U | rd = imm << 12 |
auipc rd, imm | ADD Upper Imm to PC | U | rd = PC + (imm << 12) |
pseudoinstruction | Base Instruction(s) | Meaning | Description © |
---|---|---|---|
nop | addi x0, x0, 0 | No operation | |
neg rd, rs | sub rd, x0, rs | Two’s complement | rd = -rs |
位运算指令
Instruction | Name | FMT | Description © |
---|---|---|---|
xor rd, rs1, rs2 | XOR | R | rd = rs1 ^ rs2 |
or rd, rs1, rs2 | OR | R | rd = rs1 | rs2 |
and rd, rs1, rs2 | AND Immediate | R | rd = rs1 & rs2 |
xori rd, rs1, imm | XOR Immediate | I | rd = rs1 ^ imm |
ori rd, rs1, imm | OR Immediate | I | rd = rs1 | imm |
andi rd, rs1, imm | AND Immediate | I | rd = rs1 & imm |
pseudoinstruction | Base Instruction(s) | Meaning | Description © |
---|---|---|---|
not rd, rs | xori rd, rs, -1 | negation | rd = ~rs |
位移运算指令
Instruction | Name | FMT | Description © |
---|---|---|---|
sll rd, rs1, rs2 | Shift Left Logical | R | rd = rs1 << rs2 |
srl rd, rs1, rs2 | Shift Right Logical | R | rd = rs1 >> rs2 |
slli rd, rs1, imm | Shift Left Logical Imm | I | rd = rs1 << imm[0:4] |
srli rd, rs1, imm | Shift Right Logical Imm | I | rd = rs1 >> imm[0:4] |
sra rd, rs1, rs2 | Shift Right Arithmetic | R | rd = rs1 >> rs2 |
srai rd, rs1, imm | Shift Right Arithmetic Imm | I | rd = rs1 >> imm[0:4] |
Instruction | Name | FMT | Description © |
---|---|---|---|
slt | Set Less Than | R | rd = (rs1 < rs2) ? 1 : 0 |
sltu | Set Less Than (U) | R | rd = (rs1 < rs2) ? 1 : 0 |
slti | Set Less Than Imm | I | rd = (rs1 < imm) ? 1 : 0 |
sltiu | Set Less Than Imm (U) | I | rd = (rs1 < imm) ? 1 : 0 |
U 表示 Usigned Extended
内存读写指令
Instruction | Name | FMT | Description © |
---|---|---|---|
lb rd, imm(rs1) | Load Byte (Signed Extended) | I | rd = M[rs1+imm] [0:7] |
lh rd, imm(rs1) | Load Half (Signed Extended) | I | rd = M[rs1+imm] [0:15] |
lw rd, imm(rs1) | Load Word | I | rd = M[rs1+imm] [0:31] |
lbu rd, imm(rs1) | Load Byte (Unsigned Extended) | I | rd = M[rs1+imm] [0:7] |
lhu rd, imm(rs1) | Load Half (Unsigned Extended) | I | rd = M[rs1+imm] [0:15] |
Instruction | Name | FMT | Description © |
---|---|---|---|
sb rs2, imm(rs1) | Store Byte | S | M[rs1+imm] [0:7] = rs2[0:7] |
sh rs2, imm(rs1) | Store Half | S | M[rs1+imm] [0:15] = rs2[0:15] |
sw rs2, imm(rs1) | Store Word | S | M[rs1+imm] [0:31] = rs2[0:31] |
M 表示 Memory
pseudoinstruction | Base Instruction(s) | Meaning |
---|---|---|
li rd, immediate | Myriad sequences | Load immediate |
mv rd, rs | addi rd, rs, 0 | Copy register |
la rd, symbol | auipc rd, delta[31:12] + delta[11] addi rd, rd, delta[11:0] |
Load absolute address, where delta = symbol − pc |
条件跳转指令 (branch)
Instruction | Name | FMT | Description © |
---|---|---|---|
beq rs1, rs2, imm | Branch == | B | if (rs1 == rs2) PC += imm |
bne rs1, rs2, imm | Branch != | B | if (rs1 != rs2) PC += imm |
blt rs1, rs2, imm | Branch < | B | if (rs1 < rs2) PC += imm |
bge rs1, rs2, imm | Branch >= | B | if (rs1 >= rs2) PC += imm |
bltu rs1, rs2, imm | Branch < (U) | B | if (rs1 < rs2) PC += imm |
bgeu rs1, rs2, imm | Branch >= (U) | B | if (rs1 >= rs2) PC += imm |
U 表示 Usigned Extended
pseudoinstruction | Base Instruction(s) | Meaning | Description © |
---|---|---|---|
ble rs1, rs2, offset | bge rs2, rs1, offset | Branch if Less or Equal | if (rs1 <= rs2) PC += offset |
bleu rs1, rs2, offset | bgeu rs2, rs1, offset | Branch if Less or Equal (U) | if (rs1 <= rs2) PC += offset |
bgt rs1, rs2, offset | blt rs2, rs1, offset | Branch if Greater Than | if (rs > 0) PC += offset |
bgtu rs1, rs2, offset | bltu rs2, rs1, offset | Branch if Greater Than (U) | if (rs > 0) PC += offset |
beqz rs, offset | beq rs, x0, offset | Branch if EQual Zero | if (rs == 0) PC += offset |
bnez rs, offset | bne rs, x0, offset | Branch if Not Equal Zero | if (rs != 0) PC += offset |
bltz rs, offset | blt rs, x0, offset | Branch if Less Than Zero | if (rs < 0) PC += offset |
blez rs, offset | bge x0, rs, offset | Branch if Less or Equal Zero | if (rs <= 0) PC += offset |
bgtz rs, offset | blt x0, rs, offset | Branch if Greater Than Zero | if (rs > 0) PC += offset |
bgez rs, offset | bge rs, x0, offset | Branch if Greater or Equal Zero | if (rs >= 0) PC += offset |
无条件跳转指令
Instruction | Name | FMT | Description © |
---|---|---|---|
jal rd, label | Jump And Link | J | rd = PC+4; PC += imm |
jalr rd, imm(rs1) | Jump And Link Reg | I | rd = PC+4; PC = rs1 + imm |
jal rd, label 跳转到 label 并将 jal 下一指令的地址存放到 rd 中
jalr rd, imm(rs1) 跳转到 rs1 + imm 并将 jalr 下一指令的地址存放到 rd 中
pseudoinstruction | Base Instruction(s) | Meaning |
---|---|---|
ret | jalr x0, 0(x1) | Return from subroutine |
call offset | auipc x1, offset[31:12] + offset[11] jalr x1, offset[11:0](x1) |
Call far-away subroutine |
jalr x0, 0(x1) 表示将 ret 下一条指令的地址存放到 x0 中 (对 x0 的写操作不起作用), 并跳转到 x1 + 0 的位置 (即 x1 的位置), 其中 x1 是函数调用约定中用于存放函数返回地址的寄存器 (ra), 即 call 下一条指令的地址.
pseudoinstruction | Base Instruction(s) | Meaning |
---|---|---|
j offset | jal x0, offset | Jump |
jr rs | jalr x0, 0(rs) | Jump register |
常用伪操作 directive
.text
: 代码段, 之后跟的符号都在 .text 内
.data
: 数据段, 之后跟的符号都在 .data 内
.bss
: 未初始化数据段, 之后跟的符号都在 .bss 中
.section .foo
: 自定义段, 之后跟的符号都在 .foo 段中, .foo段名可以做修改
.align n
: 按 2 的 n 次幂字节对齐
.balign n
: 按 n 字节对齐
.global
/ .globl
: 用于定义全局符号, 使链接过程中能被其他程序文件可见, 类似于 extern
.local
: 用于定义局部符号, 仅当前程序文件可见
.string "str"
: 将字符串 str 放入内存
.byte b1,…,bn
: 在内存中连续存储 n 个 bytes
.half w1,…,wn
: 在内存中连续存储 n 个 half word (2 bytes)
.word w1,…,wn
: 在内存中连续存储 n 个 word (4 bytes)
.dword w1,…,wn
: 在内存中连续存储 n 个 double word (8字节)
.float f1,…,fn
: 在内存中连续存储 n 个单精度浮点数
.double d1,…,dn
: 在内存中连续存储 n 个双精度浮点数
函数调用约定
Register | ABI Name | Description | Saver |
---|---|---|---|
x0 | zero | Hard-wired zero | — |
x1 | ra | Return address | Caller |
x2 | sp | Stack pointer | Callee |
x3 | gp | Global pointer | — |
x4 | tp | Thread pointer | — |
x5 | t0 | Temporary/alternate link register | Caller |
x6–7 | t1–2 | Temporaries | Caller |
x8 | s0/fp | Saved register/frame pointer | Callee |
x9 | s1 | Saved register | Callee |
x10–11 | a0–1 | Function arguments/return values | Caller |
x12–17 | a2–7 | Function arguments | Caller |
x18–27 | s2–11 | Saved registers | Callee |
x28–31 | t3–6 | Temporaries | Caller |
ABI (Application Binary Interface)
零寄存器 (zero): 读取总为 0, 写入不进行任何操作.
返回地址寄存器 (ra): 用于存放函数返回的地址, 即 call 下一条指令的地址
栈指针寄存器 (sp): 用于存放栈指针
临时寄存器 (t0-t6): Callee 可能会使用这些寄存器, 所以 Callee 不保证这些寄存器中的值在函数调用过程中保持不变. 这意味着对于 Caller 来说, 如果调用 Callee 之后还要用到这些寄存器中的值, 则需要在调用 Callee 之前保存这些临时寄存器, 以及在 Callee 返回后重新恢复 t0-t6 的值, 以防止 t0-t6 的值在 Callee 中被修改.
保存寄存器 (s0-s11): Callee 需要保证这些寄存器的值在函数返回时维持函数调用之前的值. 所以一旦 Callee 在自己的函数中需要用到 s0-s11, 那么必须在使用之前将其值备份存储在栈中, 并在返回之前恢复 s0-s11 的值.
参数寄存器 (a0-a1): 用于在函数调用过程中保存第一个和第二个参数, 以及在函数返回时传递返回值;
参数寄存器 (a2-a7): 如果函数调用时需要传递更多的参数, 可以使用 a2-a7 这些寄存器. 但注意用于传递参数的寄存器最多只有 8 个, 如果还需要传递更多的参数, 则要借用栈来进行.
pseudoinstruction | Base Instruction(s) | Meaning |
---|---|---|
jal offset | jal x1, offset | Jump |
jalr rs | jalr x1, 0(rs) | Jump register |
j offset | jal x0, offset | Jump |
jr rs | jalr x0, 0(rs) | Jump register |
ret | jalr x0, 0(x1) | Return from subroutine |
call offset | auipc x1, offset[31:12] + offset[11] jalr x1, offset[11:0](x1) |
Call far-away subroutine |
tail offset | auipc x6, offset[31:12] + offset[11] jalr x0, offset[11:0](x6) |
Tail call far-away subroutine |
使用栈 (stack)
1 | void _start() { |
1 | .text # Define beginning of text section |
注意在这个过程中函数栈以及栈指针是如何声明和使用的. 在整个程序的末尾声明了 stack_start 和 stack_end 两个 label, 方便程序对栈顶 stack_end (空栈顶) 和栈底 stack_start (满栈顶) 的地址进行引用
1 | stack_start: # low address |
并在 stack_start 和 stack_end 中通过 .rept/.endr 伪操作重复声明了 12 次 .word 0, 每个 .word 0 会存储一个 32-bit 的 0 在一段连续的 word (32bits) 空间上. 即声明了一段 48 bytes 的连续空间, 并初始化为 0.
在初始化栈指针寄存器 sp 时, 通过命令
1 | la sp, stack_end |
将 stack_end 的地址存放到 sp 寄存器中, 此时 sp 指向栈底 (空栈栈顶). 在压栈时, sp 的值从高地址向低地址变化, 并始终指向栈顶元素的起始地址.
1 | addi sp, sp, -8 |
1 | start_start ------------------------------------------ stack_end |
汇编与 C 的混合编程
汇编调用 C 语言函数
1 | # test.s |
1 | // test.c |
C 语言调用汇编指令
1 | asm [volatile] ( |
1 | int foo (int a, int b) { |
1 | int foo (int a, int b) { |
1 | static void w_mscratch(reg_t x) { |
C 语言调用汇编函数
1 | extern void switch_to(struct context *next); |
1 | # void switch_to(struct context *next); |
CSR寄存器
RISCV 除了 32 个通用寄存器组之外, 每个权限级别 (Level): User / Supervisor / Machine 都还有一组额外的寄存器 – 控制与状态寄存器 (CSRs, Control and Status Registers).
高 Level 可以访问低 Level 的 CSRs, 反之不可以.
普通的 ISA 指令, 如 lw/sw 指令, 不能对 CSRs 寄存器进行操作. ISA Specification (“Zicsr” 扩展) 定义了特殊的 (专门的) CSR 指令来访问这些 CSRs 寄存器.
CSRRW (CSR Read and Write)
CSRRS (CSR Read and Set bits)
CSRRC (CSR Read and Clear bits)
CSRRWI (CSR Read and Write Immediate)
CSRRSI (CSR Read and Set bits Immediate)
CSRRCI (CSR Read and Clear bits Immediate)
Instruction | Name | Description © |
---|---|---|
csrrw rd, csr, rs1 | CSR Read and Write (Atomic) | rd = csr; csr = rs1 |
csrrs rd, csr, rs1 | CSR Read and Set bits (Atomic) | rd = csr; csr |= rs1 |
csrrw t6, mscratch, t6 # swap t6 and mscratch
pseudoinstruction | Base Instruction(s) | Meaning |
---|---|---|
csrw csr, rs | csrrw x0, csr, rs | Write CSR |
csrr rd, csr | csrrs rd, csr, x0 | Read CSR |
Machine-level CSRs
Machine Information Registers:
Privilege | Name | Description |
---|---|---|
MRO | mvendorid | Vendor ID. |
MRO | marchid | Architecture ID. |
MRO | mimpid | Implementation ID. |
MRO | mhartid | Hardware thread ID. |
Machine Trap Setup:
Privilege | Name | Description |
---|---|---|
MRW | mstatus | Machine status register. |
MRW | misa | ISA and extensions |
MRW | medeleg | Machine exception delegation register. |
MRW | mideleg | Machine interrupt delegation register. |
MRW | mie | Machine interrupt-enable register. |
MRW | mtvec | Machine trap-handler base address. |
MRW | mcounteren | Machine counter enable. |
Machine Trap Handling:
Privilege | Name | Description |
---|---|---|
MRW | mscratch | Scratch register for machine trap handlers. |
MRW | mepc | Machine exception program counter. |
MRW | mcause | Machine trap cause. |
MRW | mtval | Machine bad address or instruction. |
MRW | mip | Machine interrupt pending. |
Machine Memory Protection:
Privilege | Name | Description |
---|---|---|
MRW | pmpcfg0 | Physical memory protection configuration. |
MRW | pmpcfg1 | Physical memory protection configuration, RV32 only. |
MRW | pmpcfg2 | Physical memory protection configuration. |
MRW | pmpcfg3 | Physical memory protection configuration, RV32 only. |
MRW | pmpaddr0 | Physical memory protection address register. |
MRW | pmpaddr1 | Physical memory protection address register. |
… | … | … |
MRW | pmpaddr15 | Physical memory protection address register. |
Trap 相关的 CSRs
mepc & mret
在 RISC-V 架构中, mepc
和 mret
是与异常处理和中断返回相关的两个重要 CSR寄存器 和 CSR 指令, 主要用于 Machine 模式.
mepc
是 “Machine Exception Program Counter” 的缩写, 它是一个控制和状态寄存器(CSR), 用于保存发生异常或中断时的程序计数器 (PC) 值. 也就是说, 当一个异常或中断发生时, RISC-V 处理器会自动将当前的 PC 值保存到 mepc
寄存器中 (这里的 “自动” 是硬件上的实现), 以便异常处理程序处理完异常后知道从哪里恢复执行.
- 当发生异常或中断时, 当前执行的指令地址 (PC) 会被保存到
mepc
中; - 在异常处理程序中, 处理器通过读取和修改
mepc
来决定异常处理结束后应该跳转到哪里; - 当使用
mret
指令时, 处理器会将mepc
中的值恢复到 PC 中, 从而返回到异常或中断发生的那一条指令继续执行.
mret
是 “Machine Mode Return” 的缩写, 用于从 Machine 模式返回到之前的模式 (例如 User 模式或 Supervisor 模式). 当发生异常或中断时, 处理器会跳转到一个预定义的异常处理程序. 在异常处理程序执行完毕后, 使用 mret
指令可以将处理器状态恢复到异常或中断发生之前的状态, 并跳转回被中断的程序的地址继续执行.
- 程序正常执行, 当遇到异常或中断时, 当前的 PC 值被保存到
mepc
中, 处理器跳转到异常处理程序; - 异常处理程序执行完毕后, 调用
mret
指令; mret
指令将mepc
中的值恢复到 PC 中, 处理器返回到异常或中断发生时的地址, 继续执行被中断的指令.
Trap 相关的 CSR 寄存器
Asynchronous Trap - Interrupt 中断 (异步的 Trap)
Synchronous Trap - Exception 异常 (同步的 Trap)
Privilege | Name | Description |
---|---|---|
MRW | mtvec | Machine Trap-Vector base address. |
MRW | mepc | Machine Exception Program Counter. |
MRW | mcause | Machine trap Cause. |
MRW | mtval | Machine Trap Value. |
MRW | mstatus | Machine Status register. |
MRW | mscratch | Scratch register for machine trap handlers. |
MRW | mie | Machine Interrupt-Enable register. |
MRW | mip | Machine Interrupt Pending register. |
- mtvec (Machine Trap-Vector Base-Address): 它保存发生异常时处理器需要跳转到的地址, 即 trap_vector 函数的基地址.
- mepc (Machine Exception Program Counter): 当 trap 发生时, hart 会将发生 trap 所对应指令的地址值 (pc) 保存在 mepc 中.
- mcause (Machine Cause): 当 trap 发生时, hart 会设置该寄存器通知我们 trap 发生的原因.
- mtval (Machine Trap Value): 它保存了 exception 发生时的附加信息, 譬如访问地址出错时的地址信息, 或者执行非法指令时的指令本身. 对于其他异常, 它的值为 0.
- mstatus (Machine Status): 用于跟踪和控制 hart 的当前操作状态 (特别地, 包括关闭和打开全局中断).
- mscratch (Machine Scratch): Machine 模式下专用寄存器, 我们可以自己定义其用法, 譬如用该寄存器保存当前在 hart 上运行的 task 的上下文 (context) 的地址.
- mie (Machine Interrupt Enable): 用于进一步控制 (打开和关闭) software interrupt/timer interrupt/external interrupt.
- mip (Machine Interrupt Pending): 它列出目前已发生等待处理的中断.
mtvec (Machine Trap-Vector) 寄存器
Machine trap-vector base-address register (mtvec)
1 | MXLEN-1(31) 2 1 0 |
RV32 的 MXLEN = 32, 这里的 MXLEN - 1 = 31
WARL: Write Any Values, Reads Legal Values
BASE: Trap 入口函数 trap_vector 的基地址, 必须保证 4 字节对齐;
关于 4 字节对齐. 完整的 base_address 应该是 32 位的, 但考虑到 base_address 是关于 4 字节对齐的, 即 base_address 的值是 4 的倍数 base_address % 4 = 0, 所以 base_address 值的后两位一定是 00. 于是这里可以只用 30 位 mtvec(31:2) 就对 base_address 进行存储 (存储 base_addres 的高 30 位).
MODE: 进一步控制入口函数地址的配置方式. Trap 入口函数的形式有 2 种方式, 一种叫做 Direct 方式, 一种叫做 Vectored 方式
Value | Name | Description |
---|---|---|
0 | Direct | All exceptions set pc to BASE. |
1 | Vectored | Asynchronous interrupts set pc to BASE + 4 × cause. |
>= 2 | — | Reserved |
-
Direct (MODE = 00): 所有的 Exception 和 Interrupt 发生后 PC 都跳转到 Base 指定的地址处. 即 Trap 处理函数只有 1 个, 在 Trap 处理函数中再通过 switch-case / if-else 等方式依据 Trap 类型进行分别处理.
-
Vectored (MODE = 01): Exception 处理方式同 Direct; 但 Interrupt 的入口函数的地址以数组方式排列. BASE 中保存这个指针数组的基地址.
1
2
3
4
5
6
7————————.- (base_address)
pointer0| ——> trap_handler0
————————|- (base_addr + 1*4)
pointer1| ——> trap_handler1
————————|- (base_addr + 2*4)
pointer2| ——> trap_handler2
————————.
mcause (Machine Cause)
当 Trap 发生时, Hart 会设置该寄存器通知我们 Trap 发生的原因, 最高位 Interrupt 位为 1 时标识了当前 Trap 类型为 Interrupt, 为 0 时则标识为 Exception.
1 | MXLEN-1(31) MXLEN-2(30) 0 |
剩余的 Exception Code 用于标识具体的 Interrupt 或者 Exception 的种类
Software Interrupt 软件中断:
Interrupt | Exception Code | Description |
---|---|---|
1 | 0 | User software interrupt |
1 | 1 | Supervisor software interrupt |
1 | 2 | Reserved for future standard use |
1 | 3 | Machine software interrupt |
Time Interrupt 定时器中断:
Interrupt | Exception Code | Description |
---|---|---|
1 | 4 | User timer interrupt |
1 | 5 | Supervisor timer interrupt |
1 | 6 | Reserved for future standard use |
1 | 7 | Machine timer interrupt |
External Interrupt 外部中断:
Interrupt | Exception Code | Description |
---|---|---|
1 | 8 | User external interrupt |
1 | 9 | Supervisor external interrupt |
1 | 10 | Reserved for future standard use |
1 | 11 | Machine external interrupt |
Reserved:
Interrupt | Exception Code | Description |
---|---|---|
1 | 12-15 | Reserved for future standard use |
1 | >=16 | Reserved for platform use |
Exception 异常:
Interrupt | Exception Code | Description |
---|---|---|
0 | 0 | Instruction address misaligned |
0 | 1 | Instruction access fault |
0 | 2 | Illegal instruction |
0 | 3 | Breakpoint |
0 | 4 | Load address misaligned |
0 | 5 | Load access fault |
0 | 6 | Store/AMO address misaligned |
0 | 7 | Store/AMO access fault |
0 | 8 | Environment call from U-mode |
0 | 9 | Environment call from S-mode |
0 | 10 | Reserved |
0 | 11 | Environment call from M-mode |
0 | 12 | Instruction page fault |
0 | 13 | Load page fault |
0 | 14 | Reserved for future standard use |
0 | 15 | Store/AMO page fault |
0 | 16–23 | Reserved for future standard use |
0 | 24–31 | Reserved for custom use |
0 | 32–47 | Reserved for future standard use |
0 | 48–63 | Reserved for custom use |
0 | >=64 | Reserved for future standard use |
mstatus (Machine Status) 寄存器
Machine-mode status register
1 | ...| 12 11 10 9 8 | 7 6 5 4 | 3 2 1 0 | |
-
MIE/SIE/UIE (M/S/U Interrupt Enable)
分别用于打开 (= 1) 或关闭 (= 0) Machine/Supervisor/User 模式下的全局中断. 当 Trap 发生时, Hart 会自动将 MIE/SIE/UIE 设置为 0, 即关闭全局中断. -
MPIE/SPIE/UPIE (M/S/U Previous Interrupt Enable)
当 Trap 发生时, 分别用于保存 Machine/Supervisor/User 模式下 Trap 发生之前的 MIE/SIE/UIE 的值. -
MPP/SPP (M/S Previous Privilege)
当 Trap 发生时, 分别用于保存 Trap 发生之前的权限级别. MPP 有 2 bits, 00/01/11 分别表示 Trap 之前是 User/Supervisor/Machine 模式; SPP 有 1 位, 0/1 分别表示 Trap 之前是 User/Superior 模式; 注意没有 UPP.
Trap 发生时只能从低权限模式向高权限模式切换. 对于 Machine 模式可以从 User/Supervisor/Machine 模式切换到 (Trap) 到 Machine 模式; 对于 Supervisor 模式可以从 User/Supervisor 模式 Trap 到 Supervisor 模式; 对于 User 模式只能从 User 模式切换. 所以 MPP 需要 2 bits 进行编码 (3 中情况), SPP 需要 1 bits 进行编码 (2 中情况), 而 UPP 不需要额外进行编码.mstatus 的 MPP 域与系统模式的切换有关, 并且 mstatus 上电后默认为 0. 所以上电后 MPP 默认为 00 , 即 mret 后默认进入 user 态. 如果将其设为 11, 则调用 mret 会返回到 machine 态.
mie (Machine Interrupt Enable)
mie 寄存器用于进一步控制 M/S/U 权限模式下 Software/Timer/External Interrupt 三种中断的开关. 注意区别 mstatus 寄存器的 MIE/SIE/UIE 域是三种模式中断的全局控制开关, 而 mie 是一个二级的更细分的控制开关.
1 | MXLEN-1 12 11 10 9 8 7 6 5 4 3 2 1 0 |
- MEIE/SEIE/UEIE (M/S/U External Interrupt Enable) 分别用于控制 M/S/U 模式下的外部中断
- MTIE/STIE/UTIE (M/S/U Timer Interrupt Enable) 分别用于控制 M/S/U 模式下的定时器中断
- MSIE/SSIE/USIE (M/S/U Timer Interrupt Enable) 分别用于控制 M/S/U 模式下的软件中断
mip (Machine Interrupt Pending)
mip 寄存器用于获取当前 M/S/U 模式下对应的 External/Timer/Software 中断是否发生并在等待的状态
1 | MXLEN-1 12 11 10 9 8 7 6 5 4 3 2 1 0 |
- MEIP/SEIP/UEIP (M/S/U External Interrupt Pending) 分别用于获取 M/S/U 模式下的外部中断的 Pending 状态
- MTIE/STIE/UTIE (M/S/U Timer Interrupt Pending) 分别用于获取 M/S/U 模式下的定时器中断的 Pending 状态
- MSIE/SSIE/USIE (M/S/U Timer Interrupt Pending) 分别用于获取 M/S/U 模式下的软件中断的 Pending 状态
Trap 发生流程
1. Trap 初始化:
设置入口函数, 将 trap_vector (trap 入口函数) 的地址赋给 mtvec 寄存器
2. Trap 的 Top Half:
Trap 发生, Hart 自动执行如下状态转换, 这个过程是硬件实现的, 不需要软件操作.
- 把 mstatus 的 MIE 值复制到 MPIE 中, 清除 mstatus 中的 MIE 标志位 (置 0), 效果是关闭全局中断, 防止新触发的中断打断 Trap.
- 设置 mepc; 同时 PC 被设置为 mtvec (其中保存着 trap_vector 的基地址), 即程序跳转到 trap 入口函数
- 对于 Exception, mepc 指向导致异常的指令的地址
- 对于 Interrupt, mepc 指向被中断的指令的下一条指令的地址
- 根据 trap 的种类设置 mcause, 井根据需要为 mtval 设置附加信息.
- 将 trap 发生之前的权限模式保存在 mstatus 的 MPP 域中, 再把 hart 权限模式更改为 M (也就是说无论在任何 Level 下触发 trap, hart 首先切换到 Machine 模式)
3. Trap 的 Bottom Half:
这部分为 trap 入口函数 trap_vector 所需要做的事情. 这部分是软件上的实现.
- 保存 (save) 当前控制流的上下文 (context) 信息 (利用 mscratch 寄存器)
- 调用 C 语言的 trap_handler, 其中包含着 switch-case 的逻辑, 根据 mcause 寄存器的 Exception Code 域, 分支处理不同类型的 trap
- 从 trap_handler 函数返回 (mepc 的值有可能需要调整)
- 恢复 (restore) 上下文的信息
- 执行 MRET 指令返回到 trap 之前的状态
上下文 context 信息指的是 x1-x31 除去 x0 的 31 个寄存器的状态
4. Trap 返回:
在 Trap 结束进行返回时, 不同模式会调用各自的返回命令
- mret (Machine)
- sret (Supervisor)
- uret (User)
例如在 Machine 模式下执行完 trap_vector 后会调用 mret 指令进行返回.
mret 会做 3 件事情
- 恢复权限级别: 将当前 Hart 的权限级别设为 mstatus.MPP. 再重新将 mstatus.MPP 设为 U (如果 Hart 不支持 U 则设为 M)
- 恢复中断状态: 将 mstatus.MIE 设为 mstatus.MPIE; 将 mstatus.MPIE 的值重置为 1;
- PC 跳转: pc = mepc. pc 跳转到 Trap 发生时保存的地址. 对于异常, 则为触发异常的指令的基地址, 对于中断则为触发中断的指令的下一条指令的基地址.
中断的分类
中断一共有三种, 由 Hart (Core) 内部触发的中断又称为局部中断 (Local Interrupt), 由 Hart 外部触发的中断又称为全局中断 (Global Interrupt)
- Local Interrupt: Timer Interrupt, Software Interrupt
- Global Interrupt: External Interrupt
PLIC 编程接口
PLIC, Platform-Level Interrupt Controller, 用于处理 External Interrupt (global interrupt 全局中断). local interrupt 局部中断 software interrupt 和 timer interrupt 由 CLINT (Core Local Interrupt) 处理.
1 |
|
-
Priority: PLIC_base + IRQ_id * 4
设置某一路中断源的优先级. Qemu-virt 支持 7 个优先级. 0 表示对该中断源禁用中断, 1 最低, 7 最高. 如果两个中断源优先级相同, 则根据中断源的 ID 值进一步区分优先级, ID 值越小优先级越高.1
2
*(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1; -
Pending: PLIC_base + 0x1000 + IRQ_id / 32 * 4
用于指示某一路中断源是否发生. 每个 PLIC 包含 2 个 32 位的Pending 寄存器, 每一个 bit 对应一个中断源, 如果 bit 位为 1 表示该中断源上发生了中断 (进入Pending 状态), 有待 hart 处理; 否则 bit 位为 0 表示该中断源上当前无中断发生.
Pending 寄存器的 Pending 状态可以通过 claim 方式清除.
第一个 Pending 寄存器的第 0 位对应不存在的 0 号中断源, 其值永远为0. -
Enable: PLIC_base + 0x2000 + Hart_id * 0x80 + IRQ_id / 32 * 4
针对某个 Hart 开启或关闭某一路中断源. 每个 Hart 有 2 个 Enable 寄存器 (Enable1 和 Enable2) 用于针对该 Hart 启动或者关闭某路中断源. 每个中断源对应 Enable 寄存器的一个 bit, 其中 Enable1 负责控制 1~31 号中断源; Enable2 负责控制 32 ~53 号中断源. 将对应的bit位设置为1表示使能该中断源, 否则表示关闭该中断源.1
2
*(uint32_t*)PLIC_MENABLE(hart, UART0_IRQ)= (1 << (UART0_IRQ % 32)); -
Threshold: PLIC_base + 0x200000 + Hart_id * 0x1000
针对某个 Hart 设置中断源优先级的阈值. 每个 Hart 有一个 Threshold 寄存器用于设置中断优先级的阈值. 所有小于或等于该阈值的中断源, 即使发生了也会被 PLIC 丢弃. 特别的, 当阈值设为 0 时允许所有中断源发生中断; 当阈值设为 7 时丢弃所有中断源上发生的中断.1
2
*(uint32_t*)PLIC_MTHRESHOLD(hart) = 0; -
Claim/Complete: PLIC_base + 0x200004 + Hart_id * 0x1000
Claim 和 Complete 是同一个寄存器, 每个 Hart 一个. 对该寄存器执行读操作称之为 Claim, 即获取当前发生的最高优先级的 IRQ (Interrupt Request) 的 ID. Claim 成功后会清除对应的 Pending 位. 对该寄存器执行写操作称之为 Complete, 所谓 Complete 指的是通知 PLIC 对该 IRQ 的处理已经结束.1
2
int irq = *(uint32_t*)PLIC_MCLAIM(hart);1
2
*(uint32_t*)PLIC_MCOMPLETE(hart) = irq;
中断的触发方式
以外部中断为例, 外部中断有两种触发方式: “电平触发” 和 “跳沿触发” (边沿触发)
- 电平触发方式
若外部中断定义为电平触发方式, 外部中断申请触发器的状态随着 CPU 在每个机器周期采样到的外部中断输入线的电平变化而变化, 这能提高 CPU 对外部中断请求的响应速度. 当外部中断源被设定为电平触发方式时, 在中断服务程序返回之前, 外部中断请求输入必须无效 (即变为高电平), 否则 CPU 返回主程序后会再次响应中断. 所以电平触发方式适合于外部中断以低电平输入而且中断服务程序能清除外部中断请求源 (即外部中断输入电平又变为高电平) 的情况。 - 跳沿触发方式
外部中断若定义为跳沿触发方式, 外部中断申请触发器能锁存外部中断输入线上的负跳变. 即便是 CPU 暂时不能响应, 中断申请标志也不会丢失. 在这种方式里, 如果相继连续两次采样, 一个机器周期采样到外部中断输入为高, 下一个机器周期采样为低, 则置 “1” 中断申请触发器, 直到CPU响应此中断时才清 “0”. 这样不会丢失中断, 但输入的负脉冲宽度至少保持 12 个时钟周期 (若晶振频率为 6MHz, 则为 21xs), 才能被 CPU 采样到. 外部中断的跳沿触发方式适合于以负脉冲形式输入的外部中断请求.
CLINT 编程接口
CLINT, Core Local Interruptor, 涉及到 Timer Interrupt
mtime 寄存器
内存地址: CLINT_base + 0xbff8
系统全局唯一, 在 RV32 和 RV64 上都是 64-bit. mtime 寄存器相当于一个计数器, 从系统一上电开始就一直自增 (从 0 开始自增), 硬件保证该寄存器的值始终按照一个固定的频率递增. 并且上电复位时, 硬件负责将 mtime 的值恢复为 0. QEMU 中定时器的工作频率约为 10MHz, 即每 1 秒钟, mtime 寄存器自增 10 000 000. 所以在程序中, 我们可以用 mtime 自增 10 000 000 来表示 1 秒钟的时间间隔.
1 |
mtimecmp 寄存器
内存地址: CLINT_base + 0x400 + Hart_id * 8
1 |
每个 Hart 一个 mtimecmp 寄存器, 64-bit. 上电复位时, 系统不负责设置 mtimecmp 的初值.
当 mtime >= mtimecmp 时, CLINT 会产生一个 timer 中断. 如果要 enable (使能) 定时器中断
- 需要先 ebable (使能) 全局中断, 将 mstatus 寄存器的 MIE 域 (第 3 bit 位) 置 1, 即
mstatus |= 0x00 00 00 08
- 并且将 mie 的 MTIE 域 (第 7 bit 位) 置 1, 即
mie |= 0x00 00 00 80
当 timer 中断发生时, Hart 会设置 mip 的 MTIP 域, 表示 time interrupt pending. 可以通过向 mtimecmp 中写入新的值, 清除 (置 0) mip.MTIP.
系统模式的切换
User Mode --ecall–> Machine Mode
User Mode <–eret – Machine Mode
ecall, environment call and breakpoint
系统调用底层通过 ecall 指令实现 user mode 到 machine mode 的切换, ecall 本身是通过触发中断的方式让系统进入到 trap_handler
1 | |^^^^^^^^^^^^^^^\ |