1.前言知识
1.1 CSR
在RISC-V架构中,CSR
代表控制和状态寄存器(Control and Status Register)。这些寄存器用于存储处理器的控制和状态信息。CSR
指令用于读取和写入这些寄存器,以便控制处理器的行为或获取处理器的状态信息。
常见的CSR
指令包括:
csrrw
:读取一个CSR
并写入一个新的值。csrrs
:读取一个CSR
并设置指定的位。csrrc
:读取一个CSR
并清除指定的位。csrrwi
:读取一个CSR
并写入一个立即数值。csrrsi
:读取一个CSR
并设置指定的位,使用立即数值。csrrci
:读取一个CSR
并清除指定的位,使用立即数值。
1.2 临时寄存器
在RISC-V架构中,t0
, t1
, 和 t2
是临时寄存器(temporary registers)。这些寄存器通常用于临时存储数据,并且在函数调用过程中不需要保存和恢复它们的值。
具体来说:
t0
对应寄存器x5
t1
对应寄存器x6
t2
对应寄存器x7
1.3 RISC-V地址空间
高地址
+------------------+
| 用户栈 |
| (向下增长) |
+------------------+
| 用户堆 |
| (向上增长) |
+------------------+
| BSS 段 |
| (未初始化的全局变量)|
+------------------+
| 数据段 |
| (已初始化的全局变量)|
+------------------+
| 代码段 |
| (可执行指令) |
+------------------+
| 内核栈 |
| (向下增长) |
+------------------+
| 内核堆 |
| (向上增长) |
+------------------+
| 内核数据段 |
| (已初始化的全局变量)|
+------------------+
| 内核代码段 |
| (可执行指令) |
+------------------+
低地址
- 用户栈:用户模式下的栈,向下增长。
- 用户堆:用户模式下的堆,向上增长。
- BSS 段:存储未初始化的全局变量。
- 数据段:存储已初始化的全局变量。
- 代码段:存储可执行指令。
- 内核栈:内核模式下的栈,向下增长。
- 内核堆:内核模式下的堆,向上增长。
- 内核数据段:存储内核模式下已初始化的全局变量。
- 内核代码段:存储内核模式下的可执行指令。
2.Trap汇编内容解析
2.1 完整汇编内容
.altmacro
.macro SAVE_GP n
sd x\n, \n*8(sp)
.endm
.macro LOAD_GP n
ld x\n, \n*8(sp)
.endm
.section .text
.globl __alltraps
.globl __restore
.align 2
__alltraps:
csrrw sp, sscratch, sp
# now sp->kernel stack, sscratch->user stack
# allocate a TrapContext on kernel stack
addi sp, sp, -34*8
# save general-purpose registers
sd x1, 1*8(sp)
# skip sp(x2), we will save it later
sd x3, 3*8(sp)
# skip tp(x4), application does not use it
# save x5~x31
.set n, 5
.rept 27
SAVE_GP %n
.set n, n+1
.endr
# we can use t0/t1/t2 freely, because they were saved on kernel stack
csrr t0, sstatus
csrr t1, sepc
sd t0, 32*8(sp)
sd t1, 33*8(sp)
# read user stack from sscratch and save it on the kernel stack
csrr t2, sscratch
sd t2, 2*8(sp)
# set input argument of trap_handler(cx: &mut TrapContext)
mv a0, sp
call trap_handler
__restore:
# case1: start running app by __restore
# case2: back to U after handling trap
mv sp, a0
# now sp->kernel stack(after allocated), sscratch->user stack
# restore sstatus/sepc
ld t0, 32*8(sp)
ld t1, 33*8(sp)
ld t2, 2*8(sp)
csrw sstatus, t0
csrw sepc, t1
csrw sscratch, t2
# restore general-purpuse registers except sp/tp
ld x1, 1*8(sp)
ld x3, 3*8(sp)
.set n, 5
.rept 27
LOAD_GP %n
.set n, n+1
.endr
# release TrapContext on kernel stack
addi sp, sp, 34*8
# now sp->kernel stack, sscratch->user stack
csrrw sp, sscratch, sp
sret
2.2 宏定义
.altmacro
.macro SAVE_GP n
sd x\n, \n*8(sp)
.endm
.macro LOAD_GP n
ld x\n, \n*8(sp)
.endm
这两段宏定义用于保存和加载通用寄存器。
SAVE_GP 宏将寄存器 x\n 的值存储到栈指针 sp 的偏移量 \n*8 处。
LOAD_GP 宏则从栈指针 sp 的偏移量 \n*8 处加载值到寄存器 x\n。
2.3 __alltraps
函数
.section .text
.globl __alltraps
.globl __restore
.align 2
__alltraps:
csrrw sp, sscratch, sp
# now sp->kernel stack, sscratch->user stack
# allocate a TrapContext on kernel stack
addi sp, sp, -34*8
# save general-purpose registers
sd x1, 1*8(sp)
# skip sp(x2), we will save it later
sd x3, 3*8(sp)
# skip tp(x4), application does not use it
# save x5~x31
.set n, 5
.rept 27
SAVE_GP %n
.set n, n+1
.endr
# we can use t0/t1/t2 freely, because they were saved on kernel stack
csrr t0, sstatus
csrr t1, sepc
sd t0, 32*8(sp)
sd t1, 33*8(sp)
# read user stack from sscratch and save it on the kernel stack
csrr t2, sscratch
sd t2, 2*8(sp)
# set input argument of trap_handler(cx: &mut TrapContext)
mv a0, sp
call trap_handler
切换栈指针:
csrrw sp, sscratch, sp
将当前栈指针sp
保存到sscratch
寄存器,并将sscratch
的值(用户栈指针)加载到sp
(切换到内核栈)。分配陷阱上下文:
addi sp, sp, -34*8
在内核栈上分配空间,用于保存陷阱上下文。保存通用寄存器:使用
sd
指令和SAVE_GP
宏保存寄存器x1
到x31
的值到栈中。保存状态寄存器:
csrr t0, sstatus
和csrr t1, sepc
分别读取sstatus
和sepc
寄存器的值,并将它们保存到栈中。保存用户栈指针:
csrr t2, sscratch
读取sscratch
(用户栈指针)的值,并将其保存到栈中。调用陷阱处理程序:将栈指针
sp
传递给a0
寄存器,并调用trap_handler
函数。
在 RISC-V 架构中,栈是向下增长的,这意味着栈指针(
sp
)的值在分配新空间时会减小。具体来说,当你需要在栈上分配空间时,你会减小栈指针的值,以便为新的数据腾出空间。在代码中,
addi sp, sp, -34*8
这条指令的作用是将栈指针sp
向下移动 34 个 8 字节的单位(总共 272 字节),为TrapContext
分配空间。这样做是为了确保有足够的空间来保存所有需要的寄存器和其他数据。如果使用
addi sp, sp, +34*8
,则会将栈指针向上移动,这样会覆盖栈上已有的数据,导致程序运行出错。
2.4 __restore
函数
__restore:
# case1: start running app by __restore
# case2: back to U after handling trap
mv sp, a0
# now sp->kernel stack(after allocated), sscratch->user stack
# restore sstatus/sepc
ld t0, 32*8(sp)
ld t1, 33*8(sp)
ld t2, 2*8(sp)
csrw sstatus, t0
csrw sepc, t1
csrw sscratch, t2
# restore general-purpuse registers except sp/tp
ld x1, 1*8(sp)
ld x3, 3*8(sp)
.set n, 5
.rept 27
LOAD_GP %n
.set n, n+1
.endr
# release TrapContext on kernel stack
addi sp, sp, 34*8
# now sp->kernel stack, sscratch->user stack
csrrw sp, sscratch, sp
sret
恢复栈指针:
mv sp, a0
将a0
寄存器的值(内核栈指针)加载到sp
。恢复状态寄存器:从栈中加载
sstatus
、sepc
和sscratch
的值,并将它们恢复到相应的寄存器中。恢复通用寄存器:使用
ld
指令和LOAD_GP
宏从栈中恢复寄存器x1
到x31
的值。释放陷阱上下文:
addi sp, sp, 34*8
释放在内核栈上分配的陷阱上下文空间。切换回用户栈:
csrrw sp, sscratch, sp
将sscratch
的值(用户栈指针)加载回sp
,并将当前栈指针sp
保存到sscratch
。返回用户模式:
sret
指令返回到用户模式。
3.引用
rCore学习章节:https://rcore-os.cn/rCore-Tutorial-Book-v3/chapter2/4trap-handling.html