内核中断机制
中断代理
1 | // delegate all interrupts and exceptions to supervisor mode. |
首先,我们在bootloader阶段进行中断代理,此时,我们仍处在M特权级,而我们的内核需要运行在S特权级。默认情况下,所有的陷阱中断都会在M态进行处理,因此我们需要将其代理到S态以便我们进行处理。
这里首先简要介绍一下medeleg(machine exception delegation register),mideleg(machine interrupt delegation register)以及sie(supervisor interrupt register)寄存器的作用:
medeleg和mideleg表明表明当前的中断或者异常应当处理在更低的特权级。在系统中我们拥有3个特权级(M/S/U),通过设置medeleg和mideleg可以将发生在系统的中断代理到S态进行处理。如果支持U态的话,我们也可以通过设置sedeleg和sideleg寄存器来将中断代理到U态进行处理。
而sie则是中断使能寄存器的意思,0-15位被分配为标准中断原因,16位以上则为特定的平台或者客户端所设置。
在这里我们需要设置sie寄存器为可写的并且设置其SEIE、STIE以及SSIE位。
其中,SEIE设置表明开启S态外部中断;STIE表示开启S态时钟中断;SSIE设置表明开启S态软件中断。
开启中断
首先我们在trapinit函数里面将kernelvec作为地址写入stvec寄存器中,stvec寄存器为Supervisor Trap Vector Base Address Register,包括向量基地址以及向量模式。如此一来,当我们的操作系统内核检测到发生中断后,就去stvec去查看处理陷阱函数的地址,随后进入其中进行陷阱处理。
其中,我们的陷阱处理函数由一段汇编表示:
1 | .section .text |
可以看到,我们将必要的寄存器值保存进栈里,然后调用kerneltrap这个函数进行内核陷阱的处理,当我们执行完kerneltrap函数返回后,我们将栈内容恢复,并将保存的上下文内容恢复,随后执行sret指令返回发生陷阱的指令后继续进行运行。
开启PLIC
PLIC为the riscv Platform Level Interrupt Controller,其作用如下图所示:


可以看到PLIC接收到中断后将其转发给不同的Hart来使其处理。
PLIC结构如上图所示, 当PLIC接收到中断信号后,在内部进行逻辑的信号处理,最后输出信号给对应的CPU进行处理。
而在QEMU平台下,我们不需要去接入具体的外部设备,只需要向QEMU提供的测试地址内写入一些值,即可开启外部中断的检测。
例如在xv6-rust中的plicinit函数:
1 | pub unsafe fn plicinit(){ |
我们只需要将对应的外设的值按规定写入对应的物理地址中,即可开启外设的外部中断机制。
内核中断处理
当我们进入内核中断处理函数(即kerneltrap)后,我们需要通过scause寄存器的值来获取发生中断或异常的原因,从而根据不同的原因进行处理。而与此同时,我们也需要获取去读取sstatus和sepc寄存器来进行中断间的上下文切换。
其中,sepc寄存器记录了中断或者异常发生的虚拟地址,当我们的中断结束之后,RISC-V会将sepc寄存器的值写入程序计数器以便继续执行,因此我们需要将sepc的值进行修改并重新写入sepc以保存上下文。
同样,sstatus寄存器保存的是当前发生中断或者异常所在特权级,当中断或异常发生在U态的时候,sstatus的SPP位被置为0,然后sret指令后将返回U态;当sstatus的SPP位被置为1时,sret指令将返回S态,这时SPP位仍然被置为0。
1 | // interrupts and exceptions from kernel code go here via kernelvec, |
其中,devintr函数则检测其是否为外部中断,时钟中断以及其它中断或异常。




