在ARM公司的RISC指令集Cortex-M3系列MCU中,异常与中断的实现与处理非常简单。对于串口接收中断函数如下:
void USART1_IRQHandler(void){ uint8_t ret = 0; if (USART_GetIntStatus(USART1, USART_INT_RXDNE) != RESET) { uart_rx.data[uart_rx.length] = USART_ReceiveData(USART1); uart_rx.length++; } }
复制代码void SysTick_Handler(void){ timeline_ms++; }
复制代码#Save caller registers.macro SAVE_CONTEXT addi sp, sp, -20*REGBYTES STORE x1, 0*REGBYTES(sp) STORE x4, 1*REGBYTES(sp) STORE x5, 2*REGBYTES(sp) STORE x6, 3*REGBYTES(sp) STORE x7, 4*REGBYTES(sp) STORE x10, 5*REGBYTES(sp) STORE x11, 6*REGBYTES(sp) STORE x12, 7*REGBYTES(sp) STORE x13, 8*REGBYTES(sp) STORE x14, 9*REGBYTES(sp) STORE x15, 10*REGBYTES(sp) STORE x16, 11*REGBYTES(sp) STORE x17, 12*REGBYTES(sp) STORE x28, 13*REGBYTES(sp) STORE x29, 14*REGBYTES(sp) STORE x30, 15*REGBYTES(sp) STORE x31, 16*REGBYTES(sp) #restore caller registers .macro RESTORE_CONTEXT LOAD x1, 0*REGBYTES(sp) LOAD x4, 1*REGBYTES(sp) LOAD x5, 2*REGBYTES(sp) LOAD x6, 3*REGBYTES(sp) LOAD x7, 4*REGBYTES(sp) LOAD x10, 5*REGBYTES(sp) LOAD x11, 6*REGBYTES(sp) LOAD x12, 7*REGBYTES(sp) LOAD x13, 8*REGBYTES(sp) LOAD x14, 9*REGBYTES(sp) LOAD x15, 10*REGBYTES(sp) LOAD x16, 11*REGBYTES(sp) LOAD x17, 12*REGBYTES(sp) LOAD x28, 13*REGBYTES(sp) LOAD x29, 14*REGBYTES(sp) LOAD x30, 15*REGBYTES(sp) LOAD x31, 16*REGBYTES(sp)
复制代码当年在试用RISC-V的MCU时,我曾公开表态RISC-V指令集这中断的处理代码——需要软件开发人员手动保存现场。但当时只知其然,并不知道所以然。在阅读了《RISC-V体系结构编程与实践 》书中第八章和第九章后,总算是明白了其中的缘由。下面就跟随我的读书笔记,一起来了解吧!
ARM公司的RISC指令集是以应用为主,经过多年的经验总结,MCU中的中断控制器实现复杂,实现用户软件开发方便——典型的商用客户需求角度考虑嘛。而RISC-V是新生,秉承“大道至简”的方针——其实在本文中,可以理解为方便硬件实现。换句话说,RISC-V的设计思路是方便硬件或逻辑设计人员上手。当然要苦了我们这些做软件开发的人员了。
我们在来看看RISC-V在发生异常时都分了几步?每步谁来参与了?又都做了什么?
当异常发生时,我们把处理过程分解为3部分:
1. 异常发生时 CPU做的事情
- 保存当前 PC 值到mepc 寄存器中
- 把异常的类型更新到mcause 寄存器
- 把发生异常时的虚拟地址更新到 mtval 存器中
- 保存异常发生前的中断状态,即把异常发生前的 MIE 字段保存到 mstatus 寄存器的MPE 字段中
- 保存异常发生前的处理器模式(如U 模式、S 模式等),即把异常发生前的处理器模式保存到mstatus 寄存器的MPP 字段中
- 关闭本地中断,即设置mstatus 寄存器中的MIE字段为0
- 设置处理器模式为M模式
- 跳转到异常向量表,即把 mtvec 寄存器的值设置到PC 寄存器中
2. 操作系统需要做的事情
操作系练需要做的事情是从异常向量表开始的。RISC-V 支持两种异常向量表处理模式。一种是直接跳转模式,另一种是异常向量模式。下面是操作系统需要做的事情。
- 保在异常发生时的上下文,上下文包括所有通用存器的值和部分 M模式下的寄存器的值。上下文需要保存到栈里。
- 查询 mcause 寄存器中的异常以及中断编号,跳转到合适的异常处理程序中
- 异常或者中断处理完成之后,恢复保存在栈里的上下文
- 执行MRET指令,返回异常现场
3. 异常返回
- 恢复设置 MIE 字段。把 mstatus 寄存器中的 MPIE 字段设置到 mstatus 寄存器的MIE段,恢复触发异常前的中断使能状态,通常这相当于使能了本地中断
- 将处理器模式设置成之前保存到 MPP字段的处理器模式
- 把mepc寄存器保存的值设置到PC寄存器中,即返回异常触发的现场
RISC-V指令集的中断设计其实是设计的非常简单,只是大量的工作交由软件来做了而已。RISC-V中的中断与异常还是有很多可说的,比如异常与中断的区别?中断控制器如何实现中断嵌套?等等。限于篇幅,不再继续了。更多更权威的解读,大家直接下载RISC-V的技术文档——反正字也不多~~