2团
Published on 2024-08-15 / 11 Visits
0
0

rCore学习—Trap汇编内容解析

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

这两段宏定义用于保存和加载通用寄存器。

  1. SAVE_GP 宏将寄存器 x\n 的值存储到栈指针 sp 的偏移量 \n*8 处。

  2. 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
  1. 切换栈指针csrrw sp, sscratch, sp 将当前栈指针 sp 保存到 sscratch 寄存器,并将 sscratch 的值(用户栈指针)加载到 sp(切换到内核栈)。

  2. 分配陷阱上下文addi sp, sp, -34*8 在内核栈上分配空间,用于保存陷阱上下文。

  3. 保存通用寄存器:使用 sd 指令和 SAVE_GP 宏保存寄存器 x1x31 的值到栈中。

  4. 保存状态寄存器csrr t0, sstatuscsrr t1, sepc 分别读取 sstatussepc 寄存器的值,并将它们保存到栈中。

  5. 保存用户栈指针csrr t2, sscratch 读取 sscratch(用户栈指针)的值,并将其保存到栈中。

  6. 调用陷阱处理程序:将栈指针 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
  1. 恢复栈指针mv sp, a0a0 寄存器的值(内核栈指针)加载到 sp

  2. 恢复状态寄存器:从栈中加载 sstatussepcsscratch 的值,并将它们恢复到相应的寄存器中。

  3. 恢复通用寄存器:使用 ld 指令和 LOAD_GP 宏从栈中恢复寄存器 x1x31 的值。

  4. 释放陷阱上下文addi sp, sp, 34*8 释放在内核栈上分配的陷阱上下文空间。

  5. 切换回用户栈csrrw sp, sscratch, spsscratch 的值(用户栈指针)加载回 sp,并将当前栈指针 sp 保存到 sscratch

  6. 返回用户模式sret 指令返回到用户模式。

3.引用

rCore学习章节:https://rcore-os.cn/rCore-Tutorial-Book-v3/chapter2/4trap-handling.html


Comment