1. 前言
RISC-V 体系结构定义了以下寄存器:
通用寄存器 (General Purpose Registers):
x0
- 硬编码为零x1
- 返回地址 (ra)x2
- 栈指针 (sp)x3
- 全局指针 (gp)x4
- 线程指针 (tp)x5
- 临时寄存器 (t0)x6
- 临时寄存器 (t1)x7
- 临时寄存器 (t2)x8
- 保存寄存器/帧指针 (s0/fp)x9
- 保存寄存器 (s1)x10
- 函数参数/返回值 (a0)x11
- 函数参数/返回值 (a1)x12
- 函数参数 (a2)x13
- 函数参数 (a3)x14
- 函数参数 (a4)x15
- 函数参数 (a5)x16
- 函数参数 (a6)x17
- 函数参数 (a7)x18
- 保存寄存器 (s2)x19
- 保存寄存器 (s3)x20
- 保存寄存器 (s4)x21
- 保存寄存器 (s5)x22
- 保存寄存器 (s6)x23
- 保存寄存器 (s7)x24
- 保存寄存器 (s8)x25
- 保存寄存器 (s9)x26
- 保存寄存器 (s10)x27
- 保存寄存器 (s11)x28
- 临时寄存器 (t3)x29
- 临时寄存器 (t4)x30
- 临时寄存器 (t5)x31
- 临时寄存器 (t6)
浮点寄存器 (Floating Point Registers):
f0
-f31
- 浮点寄存器
特殊寄存器 (Special Registers):
pc
- 程序计数器 (Program Counter)
这些寄存器在 RISC-V 指令集中被广泛使用,用于各种计算和数据存储操作。
2. 汇编指令——sd/ld
在RISC-V汇编语言中,sd
和ld
是用于存储和加载数据的指令。
sd
(Store Doubleword): 将一个64位的寄存器值存储到内存中。语法:
sd source_register, offset(base_register)
例如:
sd s0, 8(a0)
将寄存器s0
的值存储到a0
寄存器地址加上偏移量 8 的内存位置。
ld
(Load Doubleword): 从内存中加载一个64位的值到寄存器中。语法:
ld destination_register, offset(base_register)
例如:
ld s0, 8(a0)
从a0
寄存器地址加上偏移量 8 的内存位置加载一个值到寄存器s0
。
3. 切换函数
3.1 汇编内容
.altmacro
.macro SAVE_SN n
sd s\n, (\n+2)*8(a0)
.endm
.macro LOAD_SN n
ld s\n, (\n+2)*8(a1)
.endm
.section .text
.globl __switch
__switch:
# __switch(
# current_task_cx_ptr: *mut TaskContext,
# next_task_cx_ptr: *const TaskContext
# )
# save kernel stack of current task
sd sp, 8(a0)
# save ra & s0~s11 of current execution
sd ra, 0(a0)
.set n, 0
.rept 12
SAVE_SN %n
.set n, n + 1
.endr
# restore ra & s0~s11 of next execution
ld ra, 0(a1)
.set n, 0
.rept 12
LOAD_SN %n
.set n, n + 1
.endr
# restore kernel stack of next task
ld sp, 8(a1)
ret
3.2 文件解析
在 RISC-V 中,a
寄存器(a0
到 a7
)用于传递函数参数和返回值,而 s
寄存器(s0
到 s11
)用于保存寄存器。虽然 a
寄存器只有 8 个,但 s
寄存器有 12 个(s0
到 s11
)。
在代码中,SAVE_SN
和 LOAD_SN
宏用于保存和恢复 s
寄存器。具体来说,SAVE_SN
宏将 s
寄存器的值存储到内存中,而 LOAD_SN
宏从内存中加载 s
寄存器的值。
以下是代码的详细解释:
保存当前任务的上下文:
sd sp, 8(a0)
:将当前任务的栈指针 (sp
) 保存到a0
指向的内存地址的偏移量 8 处。sd ra, 0(a0)
:将返回地址 (ra
) 保存到a0
指向的内存地址的偏移量 0 处。使用
SAVE_SN
宏保存s0
到s11
寄存器的值到a0
指向的内存地址中。
恢复下一个任务的上下文:
ld ra, 0(a1)
:从a1
指向的内存地址的偏移量 0 处加载返回地址 (ra
)。使用
LOAD_SN
宏从a1
指向的内存地址中加载s0
到s11
寄存器的值。ld sp, 8(a1)
:从a1
指向的内存地址的偏移量 8 处加载栈指针 (sp
)。
以下是代码的详细解释:
altmacro
.macro SAVE_SN n
sd s\n, (\n+2)*8(a0) # 将 s 寄存器的值存储到 a0 指向的内存地址的偏移量 (\n+2)*8 处
.endm
.macro LOAD_SN n
ld s\n, (\n+2)*8(a1) # 从 a1 指向的内存地址的偏移量 (\n+2)*8 处加载 s 寄存器的值
.endm
.section .text
.globl __switch
__switch:
# __switch(
# current_task_cx_ptr: *mut TaskContext,
# next_task_cx_ptr: *const TaskContext
# )
# 保存当前任务的内核栈
sd sp, 8(a0)
# 保存当前执行的 ra 和 s0~s11
sd ra, 0(a0)
.set n, 0
.rept 12
SAVE_SN %n
.set n, n + 1
.endr
# 恢复下一个执行的 ra 和 s0~s11
ld ra, 0(a1)
.set n, 0
.rept 12
LOAD_SN %n
.set n, n + 1
.endr
# 恢复下一个任务的内核栈
ld sp, 8(a1)
ret
通过使用宏和循环,代码能够有效地保存和恢复所有 12 个 s
寄存器的值。