文章目录
- 寄存器与别名
- 函数入栈
- 示例代码
- 作用
- 为什么需要保存
- 函数出栈
- 示例代码
- 作用
- 为什么需要恢复
- 浮点寄存器的保存
- 示例代码
- 作用
- 浮点寄存器的恢复
- 示例代码
- 作用
寄存器与别名
-
Caller
(调用者)指的是调用(或执行)一个函数的代码段或函数。它是主动发起函数调用的一方。 -
Callee
(被调用者)指的是被调用的函数本身。它是被动接受函数调用并执行相应操作的一方。
简而言之,caller
是调用函数的一方,而 callee
是被调用函数的一方。
所以函数调用时需要保存的寄存器为
- 通用寄存器 16 个
- 浮点寄存器 20 个(需要注意的是,
浮点状态寄存器
一般也是需要保存的,所以一般为 21 个)
函数入栈
示例代码
/* RV32I caller registers + 21 FPU caller registers */
#define CONTEXT_REG_NUM (4*(16 + 21))
__asm volatile("addi sp, sp, %0" : : "i"(-CONTEXT_REG_NUM) :);\
__asm volatile("sw ra, 0*4(sp) \n\
sw t0, 1*4(sp) \n\
sw t1, 2*4(sp) \n\
sw t2, 3*4(sp) \n\
sw a0, 4*4(sp) \n\
sw a1, 5*4(sp) \n\
sw a2, 6*4(sp) \n\
sw a3, 7*4(sp) \n\
sw a4, 8*4(sp) \n\
sw a5, 9*4(sp) \n\
sw a6, 10*4(sp) \n\
sw a7, 11*4(sp) \n\
sw t3, 12*4(sp) \n\
sw t4, 13*4(sp) \n\
sw t5, 14*4(sp) \n\
sw t6, 15*4(sp)"); \
SAVE_FPU_CONTEXT(); \
作用
#define CONTEXT_REG_NUM (4*(16 + 21))
这一行定义了一个常量 CONTEXT_REG_NUM ,它的值是16和21之和乘以4的结果。这个值表示需要保存的寄存器数量。
__asm volatile("addi sp, sp, %0" : : "i"(-CONTEXT_REG_NUM) :);\
这一行使用 addi 指令将 CONTEXT_REG_NUM 的值从堆栈指针( sp )减去。它有效地在堆栈上为保存的寄存器分配空间。 volatile 关键字确保编译器不会优化这段汇编代码。
剩下的代码使用 sw
(存储字)指令将特定寄存器的值保存到堆栈中。每个 sw 指令将一个寄存器的值存储在距离堆栈指针( sp )的偏移量处。偏移量通过将寄存器编号乘以4(因为每个字的大小为4个字节)并加到堆栈指针上来计算。
在这段代码中保存的寄存器是:
- ra (返回地址寄存器)
- t0 到 t6 (临时寄存器)
- a0 到 a7 (参数寄存器)
RISC-V 这段内联汇编代码是用来保存寄存器的值到栈上的。具体来说,它将寄存器的值保存在栈指针(sp)指向的内存位置上,每个寄存器的值占据4个字节的空间。
这段代码使用了RISC-V的汇编指令 sw
,它用于将寄存器的值存储到内存中。每个 sw 指令都有两个操作数,第一个操作数是要存储的寄存器,第二个操作数是存储位置的地址偏移量。
具体来说,这段代码将以下寄存器的值保存到栈上:
- ra
- t0
- t1
- t2
- a0
- a1
- a2
- a3
- a4
- a5
- a6
- a7
- t3
- t4
- t5
- t6
这段代码通常用于在函数调用时保存寄存器的值,以便在函数返回时能够正确地恢复这些寄存器的值。这样可以确保函数调用过程中不会破坏这些寄存器中的值。
- 需要注意的是,这段代码是内联汇编,嵌入在C或C++代码中。它使用了 asm 关键字来指示编译器这是一段汇编代码,并使用 volatile 来确保编译器不会对这段代码进行优化
SAVE_FPU_CONTEXT();
保存浮点寄存器见下文。
为什么需要保存
函数调用时需要保存这些寄存器的原因是为了保护调用者的寄存器值,以便函数执行时不会干扰调用者的代码。
当一个函数被调用时,它会使用一些寄存器来保存重要的值或临时变量。然而,这些寄存器的值可能会在函数执行过程中被修改。为了确保函数的正确执行,需要保存这些寄存器的值,以便在函数执行完毕后能够恢复调用者原来的寄存器值。
具体来说,保存这些寄存器的目的是为了:
- 保护调用者的返回地址(ra寄存器),以便函数执行完毕后能够正确返回到调用者的位置。
- 保护调用者的临时寄存器(t0-t6),以便函数执行期间可以使用这些寄存器来保存临时值,而不会干扰调用者的代码。
- 保护调用者的参数寄存器(a0-a7),以便函数执行期间可以使用这些寄存器来访问传递给函数的参数,而不会干扰调用者的代码。
通过将这些寄存器的值保存到堆栈中,函数可以在执行期间使用堆栈空间来保存自己的局部变量和临时值,而不会覆盖调用者的寄存器值。在函数执行完毕后,可以从堆栈中恢复保存的寄存器值,然后返回到调用者。
这种做法可以确保调用者的代码的完整性,并确保函数可以安全地被调用和返回,而不会产生意外的副作用。
函数出栈
示例代码
__asm volatile("lw ra, 0*4(sp) \n\
lw t0, 1*4(sp) \n\
lw t1, 2*4(sp) \n\
lw t2, 3*4(sp) \n\
lw a0, 4*4(sp) \n\
lw a1, 5*4(sp) \n\
lw a2, 6*4(sp) \n\
lw a3, 7*4(sp) \n\
lw a4, 8*4(sp) \n\
lw a5, 9*4(sp) \n\
lw a6, 10*4(sp) \n\
lw a7, 11*4(sp) \n\
lw t3, 12*4(sp) \n\
lw t4, 13*4(sp) \n\
lw t5, 14*4(sp) \n\
lw t6, 15*4(sp) \n");\
RESTORE_FPU_CONTEXT(); \
__asm volatile("addi sp, sp, %0" : : "i"(CONTEXT_REG_NUM) :);\
作用
RISC-V 内联汇编代码,用于从栈中恢复寄存器的值。它使用了RISC-V的汇编指令lw(load word)来加载栈中的值到相应的寄存器中。
具体解释如下:
- lw ra, 0 * 4(sp) :从栈中加载值到寄存器ra(返回地址寄存器)中。栈指针(sp)加上0 * 4表示栈中的偏移量为0,乘以4是因为每个字(word)的大小为4字节。
- lw t0, 1 * 4(sp) :从栈中加载值到寄存器t0中,偏移量为1 * 4。
- lw t1, 2 * 4(sp) :从栈中加载值到寄存器t1中,偏移量为2 * 4。
- 依此类推,后续指令将从栈中加载值到其他寄存器中,每个寄存器的偏移量都会增加。
具体来说,这段代码将从栈上恢复以下寄存器的值:
- ra
- t0
- t1
- t2
- a0
- a1
- a2
- a3
- a4
- a5
- a6
- a7
- t3
- t4
- t5
- t6
这段代码的作用是将之前保存在栈上的寄存器值恢复到相应的寄存器中,通常用于函数返回之前的清理工作。这种技术被称为函数调用的栈帧恢复。
- 需要注意的是,这段代码是内联汇编,嵌入在C或C++代码中。它使用了 asm 关键字来指示编译器这是一段汇编代码,并使用 volatile 来确保编译器不会对这段代码进行优化
__asm volatile("addi sp, sp, %0" : : "i"(CONTEXT_REG_NUM) :);\
这部分代码使用 RISC-V 汇编语言的 addi
指令来修改堆栈指针 sp
的值,以恢复堆栈的大小。 %0
是占位符,表示在指令中使用 CONTEXT_REG_NUM
的值。
RESTORE_FPU_CONTEXT();
恢复浮点寄存器见下文。
为什么需要恢复
在程序执行过程中,函数调用会导致寄存器的值被修改。这些寄存器可能包含重要的数据或程序状态。为了确保函数调用后程序能够正确继续执行,需要在函数返回之前将寄存器的值恢复到函数调用之前的状态。
这种寄存器值的恢复通常发生在函数的退出代码中,以确保程序能够正确地返回到调用函数的位置。如果不进行寄存器值的恢复,函数的返回值可能会出错,导致程序的行为不可预测。
浮点寄存器的保存
示例代码
#define SAVE_FPU_CONTEXT() { \
__asm volatile("fsw ft0, 21*4(sp) \n\
fsw ft1, 22*4(sp) \n\
fsw ft2, 23*4(sp) \n\
fsw ft3, 24*4(sp) \n\
fsw ft4, 25*4(sp) \n\
fsw ft5, 26*4(sp) \n\
fsw ft6, 27*4(sp) \n\
fsw ft7, 28*4(sp) \n\
fsw fa0, 29*4(sp) \n\
fsw fa1, 30*4(sp) \n\
fsw fa2, 31*4(sp) \n\
fsw fa3, 32*4(sp) \n\
fsw fa4, 33*4(sp) \n\
fsw fa5, 34*4(sp) \n\
fsw fa6, 35*4(sp) \n\
fsw fa7, 36*4(sp) \n\
fsw ft8, 37*4(sp) \n\
fsw ft9, 38*4(sp) \n\
fsw ft10, 39*4(sp) \n\
fsw ft11, 40*4(sp) \n\
frsr t6 \n\
sw t6, 41*4(sp) \n");\
}
作用
- fsw ft0, 21 * 4(sp) :将ft0寄存器的值保存到栈指针(sp)加上21 * 4的地址处。
- fsw ft1, 22 * 4(sp) :将ft1寄存器的值保存到栈指针(sp)加上22 * 4的地址处。
- …(以此类推,保存ft2至ft11寄存器的值)
- fsw fa0, 29 * 4(sp) :将fa0寄存器的值保存到栈指针(sp)加上29 * 4的地址处。
- fsw fa1, 30 * 4(sp) :将fa1寄存器的值保存到栈指针(sp)加上30 * 4的地址处。
- …(以此类推,保存fa2至fa7寄存器的值)
- fsw ft8, 37 * 4(sp) :将ft8寄存器的值保存到栈指针(sp)加上37 * 4的地址处。
- fsw ft9, 38 * 4(sp) :将ft9寄存器的值保存到栈指针(sp)加上38 * 4的地址处。
- …(以此类推,保存ft10和ft11寄存器的值)
- frsr t6 :将
浮点状态寄存器
的值保存到t6寄存器中。 - sw t6, 41 * 4(sp) :将t6寄存器的值保存到栈指针(sp)加上41 * 4的地址处。
具体来说,这段代码将以下寄存器的值保存到栈上:
- ft0
- ft1
- ft2
- ft3
- ft4
- ft5
- ft6
- ft7
- fa0
- fa1
- fa2
- fa3
- fa4
- fa5
- fa6
- fa7
- ft8
- ft9
- ft10
- ft11
- 浮点状态寄存器
通过这段代码,浮点寄存器的值和浮点状态寄存器的值被保存到了指定的内存位置,以便稍后可以恢复它们的值
浮点寄存器的恢复
示例代码
#define RESTORE_FPU_CONTEXT() { \
__asm volatile("flw ft0, 21*4(sp) \n\
flw ft1, 22*4(sp) \n\
flw ft2, 23*4(sp) \n\
flw ft3, 24*4(sp) \n\
flw ft4, 25*4(sp) \n\
flw ft5, 26*4(sp) \n\
flw ft6, 27*4(sp) \n\
flw ft7, 28*4(sp) \n\
flw fa0, 29*4(sp) \n\
flw fa1, 30*4(sp) \n\
flw fa2, 31*4(sp) \n\
flw fa3, 32*4(sp) \n\
flw fa4, 33*4(sp) \n\
flw fa5, 34*4(sp) \n\
flw fa6, 35*4(sp) \n\
flw fa7, 36*4(sp) \n\
flw ft8, 37*4(sp) \n\
flw ft9, 38*4(sp) \n\
flw ft10, 39*4(sp) \n\
flw ft11, 40*4(sp) \n\
lw t6, 41*4(sp) \n\
fssr t6, t6 \n");\
}
作用
代码中的 __asm volatile
表示内联汇编代码的开始。
flw
指令用于从栈指针(sp)的特定偏移位置加载浮点寄存器的值。例如, flw ft0, 21 * 4(sp) 表示将栈指针(sp)偏移21 * 4字节处的值加载到ft0寄存器中。
这段代码一共恢复了20个浮点寄存器的值,从ft0到ft11和fa0到fa7。
此外,还使用 lw 指令将栈指针(sp)偏移41 * 4字节处的值加载到 t6 寄存器中,并使用 fssr 指令将 t 6寄存器的值存储到浮点状态和控制寄存器中。 其实就是将浮点保存时保存在栈上的浮点状态寄存器恢复到浮点状态寄存器中
具体来说,这段代码从栈上恢复以下寄存器的值:
- ft0
- ft1
- ft2
- ft3
- ft4
- ft5
- ft6
- ft7
- fa0
- fa1
- fa2
- fa3
- fa4
- fa5
- fa6
- fa7
- ft8
- ft9
- ft10
- ft11
- 浮点状态寄存器
因此,这段代码的作用是从栈中恢复浮点寄存器的值,以便在程序中继续使用之前保存的浮点寄存器上下文。