中断和异常¶
By: howardlau1999
简介¶
在使用电脑的时候,你可以一边听音乐一边上网冲浪、一边在线聊天一边看视频,后台可能还挂着东西在下载。然而,真正同时运行的程序数量最多不会超过 CPU 的核心数,一般的电脑有 4 核,所以最多只能同时运行 4 个程序。另外,当你写的程序出 Bug 的时候,操作系统会帮你结束程序,并将程序结束的原因报告给你,而不会影响到其他程序的运行。
你或许已经知道,我们日常所使用的操作系统实际上是会不停地切换程序。然而,一旦程序开始运行,除非它主动跳转到其他程序,否则将永远独占一个核心,哪怕它是在死循环。这样的情况下,操作系统又如何获得 CPU 核心的使用权呢?答案是,我们日常所使用的 Windows 和 Linux 等操作系统是抢占式调度的。在操作系统启动的时候,它们会打开 CPU 上的定时器中断,也就是说,每隔一段时间,CPU 就会强行暂停当前运行的程序,并跳转到操作系统设置的中断处理程序中。这时,操作系统就有机会保存好进程的状态,并执行调度算法选择下一个需要运行的程序了。当然,除了抢占式调度,还有协作式调度,在这种调度方式中,需要程序员在代码中手动插入跳转到操作系统的代码,否则程序将一直运行下去。
而在程序出 Bug 的时候,例如访存越界、除零错误等,则会触发 CPU 中的异常,这时,CPU 同样会强行暂停目前的程序,跳转到操作系统预先设置好的异常处理程序中。这样,操作系统就可以记录下错误的原因,并调用程序员设置好的异常处理程序来处理错误。当然,你也很少编写自己的异常处理程序,那么,操作系统便会默认地将你的进程结束掉了。
在本实验中,你将通过实践了解 CPU 的中断和异常是如何实现的,CPU 是如何打断正常的执行流进入中断和异常处理程序的,又是如何告诉中断处理程序必要的信息,以及如何从中断处理程序返回到正常的执行流中。
CSR¶
CSR (Control and Status Registers, 控制和状态寄存器) 用来控制和保存 CPU 的其他功能的状态,例如中断使能状态、特权等级等。
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
指令从中断中返回,恢复原来程序的执行。
此外,中断处理程序本身也需要栈来保存函数栈帧,所以中断处理程序中还需要切换栈。一种办法是直接使用原来程序的栈,但这样有可能会导致栈溢出。为了安全考虑,中断处理程序使用单独的内存空间作为其运行栈。上面所说的保存寄存器内容,可以直接保存在中断栈上。