Skip to content

swtch.S

#
# Context switch (上下文切换)
#
#   void swtch(struct context *old, struct context *new);
#
# 寄存器 a0: 指向旧上下文 (struct context *old) 的指针
# 寄存器 a1: 指向新上下文 (struct context *new) 的指针
#
# swtch 函数负责在两个内核线程之间切换。
# 它通过保存当前线程的寄存器(CPU状态)到 old 指向的 context 结构体,
# 然后从 new 指向的 context 结构体中加载新线程的寄存器来实现。
#

# .globl swtch 使得 swtch 标签对链接器可见,
# 这样其他C文件就可以调用这个汇编函数。
.globl swtch
swtch:
        # 保存旧线程的上下文
        # 根据 RISC-V 调用约定,ra (返回地址) 和 s0-s11 (被调用者保存的寄存器)
        # 是被调用函数(callee)需要负责保存的。
        # sp (栈指针) 也需要保存,因为它定义了当前线程的栈。
        # 这些寄存器共同构成了线程的执行上下文。
        #
        # struct context 定义在 kernel/proc.h 中,其字段顺序必须与这里的保存/加载顺序严格匹配。
        # a0 寄存器中存放的是 old context 的地址。
        # sd 指令 (store double-word) 将一个64位寄存器的值存入内存。
        sd ra, 0(a0)      # 保存返回地址 (return address)
        sd sp, 8(a0)      # 保存栈指针 (stack pointer)
        sd s0, 16(a0)     # 保存被调用者保存的寄存器 s0 (callee-saved register s0)
        sd s1, 24(a0)     # 保存 s1
        sd s2, 32(a0)     # 保存 s2
        sd s3, 40(a0)     # 保存 s3
        sd s4, 48(a0)     # 保存 s4
        sd s5, 56(a0)     # 保存 s5
        sd s6, 64(a0)     # 保存 s6
        sd s7, 72(a0)     # 保存 s7
        sd s8, 80(a0)     # 保存 s8
        sd s9, 88(a0)     # 保存 s9
        sd s10, 96(a0)    # 保存 s10
        sd s11, 104(a0)   # 保存 s11

        # 加载新线程的上下文
        # a1 寄存器中存放的是 new context 的地址。
        # ld 指令 (load double-word) 从内存中加载一个64位值到寄存器。
        ld ra, 0(a1)      # 加载新线程的返回地址
        ld sp, 8(a1)      # 加载新线程的栈指针
        ld s0, 16(a1)     # 加载新线程的 s0
        ld s1, 24(a1)     # 加载新线程的 s1
        ld s2, 32(a1)     # 加载新线程的 s2
        ld s3, 40(a1)     # 加载新线程的 s3
        ld s4, 48(a1)     # 加载新线程的 s4
        ld s5, 56(a1)     # 加载新线程的 s5
        ld s6, 64(a1)     # 加载新线程的 s6
        ld s7, 72(a1)     # 加载新线程的 s7
        ld s8, 80(a1)     # 加载新线程的 s8
        ld s9, 88(a1)     # 加载新线程的 s9
        ld s10, 96(a1)    # 加载新线程的 s10
        ld s11, 104(a1)   # 加载新线程的 s11

        # 返回到新加载的返回地址 (ra)
        # ret 指令实际上是 `jalr zero, 0(ra)` 的伪指令。
        # 它会跳转到 ra 寄存器中保存的地址。
        # 在这里,它会跳转到新线程之前被切换出去的地方(例如 scheduler 函数中的某个点),
        # 或者是一个新线程的启动函数(例如 forkret)。
        #
        # 一旦 ret 执行,CPU 将在新线程的栈上,使用新线程的寄存器状态开始执行。
        # 至此,上下文切换完成。
        ret