MIT 6.S081 第四章笔记 | 陷入指令和中断

4.1 ISA & Assembly Language

\(ISA\): \(Instruction \space Set\)

\(workflow\): \(C \rightarrow Assembly(.S/.asm) \rightarrow binary(object.o)\)

汇编语言没有明确的工作流,只是一行一行的执行指令。汇编语言是基于寄存器进行操作的,而不是内存。

\(RISC-V \space vs \space x86\)

  • \(RISC-V\)是精简指令集,指令数少,效率更高,不好维护。
  • \(x86\):复杂指令集,指令很多并且可以实现复杂功能

4.2 Calling convention

调用约定(\(calling \space convention\))是规定子过程如何获取参数以及如何返回的方案,调用约定一般规定了: - 参数、返回值、返回地址等放置的位置(寄存器、栈或内存)。

RISC-V通过寄存器来传递参数,而不是栈。\(a_0 \rightarrow a_7\)int参数,\(f_0 \rightarrow f_7\)float参数。

  • 如何将调用子过程的准备工作与恢复现场的工作划分到调用者(\(caller\))与被调用者(\(callee\))身上。

小于一个指针字(RISCV64中是8字节,RISCV32是4字节)的参数传入时将参数放在寄存器的最低位,因为RISC-V是小端系统,当2个指针字的参数传入时,低位的1个指针字放在偶数寄存器,比如a0上,高位的1个指针字放在奇数寄存器,比如a1上。当高于2个指针字的参数传入时以引用的方式传入。struct参数没有传到寄存器的部分将以栈的方式传入,sp栈指针将指向第一个没有传入到寄存器的参数。

从函数返回的值,如果是整数将放在a0和a1中,如果是小数将放置在fa0和fa1寄存器中。对于更大的返回值,将放置在内存中,\(caller\) 开辟这个内存,并且把指向这个内存的指针作为第一个参数传递给 \(callee\)

\(caller\) 保存的寄存器不会在函数调用之间被保存,又名易失性寄存器,如果要在过程调用后恢复该值,则调用方需要将这些寄存器压入堆栈或复制到其他位置,而 \(callee\) 保存的寄存器会被保存,称为非易失性寄存器,可以期望这些寄存器在被调用者返回后保持相同的值。比如函数A调用了函数B,所有函数A保存的寄存器在函数B被调用后可以被B重写覆盖。

RISC-V常用寄存器及使用约定

4.3 Stack

栈从高地址向低地址增长,每个大的\(box\)叫一个\(stack \space frame\)(栈帧),栈帧由函数调用来分配,每个栈帧大小不一定一样,但是栈帧的最高处一定是\(return \space address\)

sp是stack pointer,用于指向栈顶(低地址),保存在寄存器中

fp是frame pointer,用于指向当前帧底部(高地址),保存在寄存器中,同时每个函数栈帧中保存了调用当前函数的函数(父函数)的fp(保存在to prev frame那一栏中)

这些栈帧都是由编译器编译生成的汇编文件生成的

RISC-V栈

4.4 trap

3种可能的情况使得CPU暂停对正常指令的执行,并强制将控制权转移到处理该事件的特殊代码:1. syscall,移交给kernel 2. exception,指令执行了非法操作 3. 设备中断。以上情况合并称为trap。

trap应该对于被打断的指令是透明的,也就是说被打断的指令不应该知道这个地方产生了trap,产生trap之后现场应该得以恢复并继续执行被打断的指令。

xv6对trap的处理分为四个阶段:1. 对RISC-V CPU的硬件的一些操作 2. 一些为运行kernel C语言文件做准备的汇编文件 3. 用C实现的trap handler(暂且翻译为异常处理程序) 4. system call / device-driver service routine

通常对于user space的trap、kernel space的trap和计时器中断会有不同的trap handler

4.5 RISC-V trap machinery

RISC-V CPU有一系列的控制寄存器可以通知kernel发生了trap,也可以由kernel写入来告诉CPU怎样处理trap。

  • stvec:异常处理程序的地址。由内核填入。
  • sepc: 保存trap发生时当前的PC寄存器值,之后PC的值被stvec覆盖。通过sret指令将sepc值也入PC,达到回复现场的作用。
  • scause: 填入产生trap的原因,有CPU写入。
  • sscartch: 异常处理程序使用的一组寄存器。
  • sstatus: SIE位控制设备中断是否被开启,SPP位指示trap是来自内核还是用户程序。

以上寄存器都只在监管模式被启动。

当发生除了计时器中断以外的其他类型的trap时,RISC-V将执行以下步骤:

  1. 如果trap是一个设备产生的中断,而SIE又被清除的情况下,不做下方的任何动作。
  2. 清除SIE来disable一切中断。
  3. 把pc复制到sepc。
  4. 把当前的模式(user / supervisor)保存到SPP。
  5. 设置scause寄存器来指示产生trap的原因。
  6. 将当前的模式设置为supervisor。
  7. 将stvec的值复制到pc。
  8. 开始执行pc指向的trap handler的代码。

注意CPU并没有切换到kernel页表,也没有切换到kernel栈,也不会保存除PC外的其他寄存器值。这些操作都应该由内核完成。

4.6 Traps from user space

如果用户程序进行系统调用(调用ecall指令)、执行非法操作或者设备中断时,就会产生trap。

当user space中发生trap时,会将stvec的值复制到pc,而此时stvec的值是trampoline.S中的uservec,因此跳转到uservec函数。该函数先保存一些现场的寄存器,恢复kernel栈指针、kernel page table到satp寄存器,再跳转到usertrap(kernel/trap.c)这个用户trap通用执行器。然后返回usertrapret(kernel/trap.c),跳回到kernel/trampoline.S,最后用userret(kernel/trampoline.S)通过sret跳回到user space。

RISC-V在trap中不会改变页表,因此user page table必须有对uservec的mapping,uservecstvec指向的trap vector instruction。uservec要切换satp到kernel页表,同时kernel页表中也要有和user页表中对uservec相同的映射。RISC-V将uservec保存在trampoline页中,并将TRAMPOLINE放在kernel页表和user页表的相同位置处(MAXVA)。


MIT 6.S081 第四章笔记 | 陷入指令和中断
https://acmicpc.top/2024/02/29/MIT-6.S081-lab04 Notes/
作者
江欣婷
发布于
2024年2月29日
许可协议