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

rCore学习—任务切换switch.S汇编解析

1. 前言

RISC-V 体系结构定义了以下寄存器:

  1. 通用寄存器 (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)

  2. 浮点寄存器 (Floating Point Registers):

    • f0 - f31 - 浮点寄存器

  3. 特殊寄存器 (Special Registers):

    • pc - 程序计数器 (Program Counter)

这些寄存器在 RISC-V 指令集中被广泛使用,用于各种计算和数据存储操作。

2. 汇编指令——sd/ld

在RISC-V汇编语言中,sdld是用于存储和加载数据的指令。

  • 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 寄存器(a0a7)用于传递函数参数和返回值,而 s 寄存器(s0s11)用于保存寄存器。虽然 a 寄存器只有 8 个,但 s 寄存器有 12 个(s0s11)。

在代码中,SAVE_SNLOAD_SN 宏用于保存和恢复 s 寄存器。具体来说,SAVE_SN 宏将 s 寄存器的值存储到内存中,而 LOAD_SN 宏从内存中加载 s 寄存器的值。

以下是代码的详细解释:

  1. 保存当前任务的上下文

    • sd sp, 8(a0):将当前任务的栈指针 (sp) 保存到 a0 指向的内存地址的偏移量 8 处。

    • sd ra, 0(a0):将返回地址 (ra) 保存到 a0 指向的内存地址的偏移量 0 处。

    • 使用 SAVE_SN 宏保存 s0s11 寄存器的值到 a0 指向的内存地址中。

  2. 恢复下一个任务的上下文

    • ld ra, 0(a1):从 a1 指向的内存地址的偏移量 0 处加载返回地址 (ra)。

    • 使用 LOAD_SN 宏从 a1 指向的内存地址中加载 s0s11 寄存器的值。

    • 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寄存器的值。


Comment