每个 RISC-V CPU 都有一组特权寄存器,内核写入这些控制寄存器来告诉 CPU 如何处理 trap,并且内核可以读取这些寄存器来找出已发生的 trap(在 kernel/riscv.h 中定义)。
重要特权寄存器概述:
stevc:trap handler 的地址,由内核写入,告诉sepc:保存 trap 发生时的程序寄存器(因为pc随后会被stvec中的值覆盖)。sret(从 trap 返回)指令将sepc复制到pc,可以通过编写sepc来控制sret的去向scause:放置一个数字来描述 trap 的原因sscratch:放在 trap handler 的最开始处,防止在保存用户寄存器之前覆盖它们sstatus:其中的 SIE 位控制是否启用设备中断。如果内核清除 SIE,RISC-V 将推迟设备中断,直到内核设置 SIE。SPP 位指示 trap 是来自用户模式还是管理模式,并控制sret返回的模式satp:当前页表的根地址 上述寄存器与内核模式下处理的 trap 相关,并且不能在用户模式下读取或写入。
RISC-V 硬件会对所有 trap 类型(定时器中断除外)执行以下操作:
- 中断屏蔽检查
- 如果 trap 是由设备中断引发的,且
sstatus.SIE=0,则处理器会暂存该中断,暂缓执行 - 如果是异常或系统调用,会跳过该步骤
- 如果 trap 是由设备中断引发的,且
- 禁用中断
- 设置
sstatus.SIE=0:防止 trap 处理期间被其他中断嵌套
- 设置
- 保存上下文
sepc:保存当前pc,以便 trap 返回时恢复执行sstatus.SPP:保存当前特权模式(0=user, 1=kernel)
- 设置 trap 原因
scause:记录 trap 类型(中断或异常)和具体原因(如中断号或异常码)。
- 切换管理模式
- 将当前模式设置为 Supervisor Mode,以便执行内核中的 trap handler。
- 跳转到 trap handler
- 将
stvec的地址加载到pc。 以上步骤为硬件操作,在发生 trap 后自动执行,并没有显式代码。
- 将
中断屏蔽检查中为什么区别对待?
- 设备中断的异步性
- 设备中断是异步的,可以在任何时候发生。因此操作系统需要暂时屏蔽中断,以确保某些关键代码不被中断干扰。
- 屏蔽中断时,新的中断可以被暂存,等到中断重启时再处理。
- 异常和系统调用的同步性
- 异常和系统调用是由当前指令直接触发的,是同步事件。
- 异常通常表示必须立即处理的错误或特殊情况,如果不处理,程序无法正确执行。
- 系统调用是程序主动请求内核服务,如果不处理,程序会一直等待。
未完成的步骤(需软件处理):
- 切换页表:CPU 不会自动切换页表
- 切换栈指针:CPU 不会自动切换栈
- 保存寄存器:通用寄存器需由软件保存
CPU 保留上述步骤交给软件处理是为软件提供灵活性,比如某些操作系统在某些情况下会省略页表切换,硬件仅提供最小必要的支持。而这些步骤都将在
trapline页(uservec)中执行。