Skip to content

中断和异常🚁

By: howardlau1999

简介🚁

在使用电脑的时候,你可以一边听音乐一边上网冲浪、一边在线聊天一边看视频,后台可能还挂着东西在下载。然而,真正同时运行的程序数量最多不会超过 CPU 的核心数,一般的电脑有 4 核,所以最多只能同时运行 4 个程序。另外,当你写的程序出 Bug 的时候,操作系统会帮你结束程序,并将程序结束的原因报告给你,而不会影响到其他程序的运行。

你或许已经知道,我们日常所使用的操作系统实际上是会不停地切换程序。然而,一旦程序开始运行,除非它主动跳转到其他程序,否则将永远独占一个核心,哪怕它是在死循环。这样的情况下,操作系统又如何获得 CPU 核心的使用权呢?答案是,我们日常所使用的 Windows 和 Linux 等操作系统是抢占式调度的。在操作系统启动的时候,它们会打开 CPU 上的定时器中断,也就是说,每隔一段时间,CPU 就会强行暂停当前运行的程序,并跳转到操作系统设置的中断处理程序中。这时,操作系统就有机会保存好进程的状态,并执行调度算法选择下一个需要运行的程序了。当然,除了抢占式调度,还有协作式调度,在这种调度方式中,需要程序员在代码中手动插入跳转到操作系统的代码,否则程序将一直运行下去。

而在程序出 Bug 的时候,例如访存越界、除零错误等,则会触发 CPU 中的异常,这时,CPU 同样会强行暂停目前的程序,跳转到操作系统预先设置好的异常处理程序中。这样,操作系统就可以记录下错误的原因,并调用程序员设置好的异常处理程序来处理错误。当然,你也很少编写自己的异常处理程序,那么,操作系统便会默认地将你的进程结束掉了。

在本实验中,你将通过实践了解 CPU 的中断和异常是如何实现的,CPU 是如何打断正常的执行流进入中断和异常处理程序的,又是如何告诉中断处理程序必要的信息,以及如何从中断处理程序返回到正常的执行流中。下面将带你初步了解 RISC-V 中中断和异常的相关概念知识。

中断、异常和陷入的定义🚁

本实验中中断、异常和陷入的定义都以 RISC-V 非特权级手册中第一章的定义为准:

1.6. Exceptions, Traps, and Interrupts

We use the term exception to refer to an unusual condition occurring at run time associated with an instruction in the current RISC-V hart. We use the term interrupt to refer to an external asynchronous event that may cause a RISC-V hart to experience an unexpected transfer of control. We use the term trap to refer to the transfer of control to a trap handler caused by either an exception or an interrupt.

异常(exception)指运行时与某一指令相关的不寻常情况,例如整数除以零异常、指令地址未对齐异常等。

中断(interrupt)指来自CPU外部的事件,例如键鼠输入、定时器事件等。

陷入(trap)指由异常或中断导致的 CPU 控制权切换,例如键鼠中断发生后 CPU 暂时放下当前程序,陷入至处理键鼠输入的例程,以完成键鼠操作。

控制和状态寄存器🚁

CSR (Control and Status Registers, 控制和状态寄存器) 用来控制和保存 CPU 其他功能的状态,例如中断使能状态、特权等级等。在非特权级手册的 "Zicsr" 扩展部分讲述了与 CSR 相关的指令,在特权级手册则详细描述了各个 CSR 的定义及作用。

RISC-V 中为每个CPU核心保留了 4096 个 CSR 寄存器地址(12 bits),这些地址中有的是 RISC-V 必须的,有些是可选实现的,还有些由CPU厂商自行选择。为简单期间,实验中我们仅考虑CPU处于 Machine 特权级,其拥有最高特权。User 和 Supervisor 特权级不做讨论。Machine 特权级下,相应的 CSR 名称以 m 开头,一些 RISC-V 必须的 CSR 如下所示。

mstatus, mtvec, mip&mie, mepc, mcause,

mstatus 寄存器🚁

mstatus 寄存器用于记录机器模式下的状态,例如中断是否启用等。

mepc 寄存器🚁

mepc 寄存器保存了中断返回后需要执行的指令地址,当 CPU 执行中断时,mepc 寄存器被自动设置为当前指令的地址,如果 EX 阶段正在执行跳转,则设置为跳转的目标地址。

mcause 寄存器🚁

mcause 寄存器保存了中断的原因。

mtvec 寄存器🚁

mtvec 寄存器保存了陷入处理程序的地址和设置,当 CPU 执行中断时,pc 寄存器被自动设置为 mtvec 寄存器中保存的陷入处理程序的地址。

在中断发生的时候,CPU 需要清空并阻塞流水线,并在 CSR 寄存器写入中断相关的信息。由于 CSR 寄存器堆实现只有一个读写端口,因此,CPU 需要多个周期才能完成 CSR 寄存器的写入。在设置完 CSR 寄存器后,发出控制信号,CPU 跳转到 mtvec 中保存的指令地址,开始执行中断处理程序。

中断处理程序🚁

为了尽快处理中断,CPU 仅会将最基本的中断发生地址和中断原因等保存到 CSR 寄存器中。更复杂的功能由中断处理程序实现。

程序正常执行的过程中,会使用到通用寄存器堆。为了能够让中断返回后,原来的执行流程不受影响,中断处理程序需要立刻先将当前所有寄存器的内容写入内存中,然后再调用我们自定义的函数处理中断。在函数返回后,我们需要将原来内存中的寄存器内容恢复到 CPU 中,并调用 mret 指令从中断中返回,恢复原来程序的执行。

此外,中断处理程序本身也需要栈来保存函数栈帧,所以中断处理程序中还需要切换栈。为了安全考虑,中断处理程序使用单独的内存空间作为其运行栈。上面所说的保存寄存器内容,可以直接保存在中断栈上。

CSR 相关指令🚁

csrrwcsrrscsrrc 及其相应立即数版本指令(带 i 后缀),都能读出指定 CSR 的值并同时修改 CSR 的内容。相应的指令格式和具体作用可查询 非特权级手册 的 "Zicsr" 扩展部分。