RISC-V:异常及其在Linux下的处理
首先了解RISC-V异常相关配置和寄存器,然后了解各种异常类型以及Linux下是如何处理的。
1 RISC-V异常类型
机器模式
|
超级用户模式
|
||||
异常配置寄存器组
|
处理器状态寄存器
|
MSTATUS
|
存储了处理器在机器模式下的状态和控制信息,包括全局中断有效位、异常保留中断有效位、异常保留特权模式位等。
|
|
存储了处理器在超级用户模式下的状态和控制信息,包括全局中断有效位、异常保留中断有效位、异常保留特权模式位等,是 MSTATUS 的部分映射。
|
处理器指令集特性寄存器
|
MISA
|
存储了处理器所支持的指令集架构特性。
|
|||
异常降级控制寄存器
|
MEDELEG
|
可以将超级用户和用户模式发生的异常降级到超级用户模式响应。
|
|||
中断降级控制寄存器
|
MIDELEG
|
可以将超级用户模式中断降级到超级用户模式响应。
|
|||
中断使能控制寄存器
|
MIE
|
用于控制不同中断类型的使能和屏蔽。
|
用于控制不同中断类型的使能和屏蔽,是 MIE 的部分映射。
|
||
向量基址寄存器
|
MTVEC
|
用于配置异常服务程序的入口地址。
|
用于配置异常服务程序的入口地址。
|
||
计数器访问授权寄存器
|
MCOUNTEREN
|
用于授权超级用户模式是否可以访问用户模式计数器。
|
用于授权用户模式是否可以访问用户模式计数器。
|
||
异常处理寄存器组
|
异常临时数据备份寄存器
|
MSCRATCH
|
用于处理器在异常服务程序中备份临时数据。一般用来存储机器模式本地上下文空间的入口指针值。
|
用于处理器在异常服务程序中备份临时数据。一般用来存储超级用户模式本地上下文空间的入口指针值。
|
|
异常保留程序计数器寄存器
|
MEPC
|
用于存储程序从异常服务程序退出时的程序计数器值(即 PC 值)。
|
用于存储程序从异常服务程序退出时的程序计数器值(即 PC 值)
|
||
异常事件向量寄存器
|
MCAUSE
|
用于保存触发异常的异常事件向量号,用于在异常服务程序中处理对应事件。
|
用于保存触发异常的异常事件向量号,用于在异常服务程序中处理对应事件。
|
||
中断等待状态寄存器
|
MIP
|
用于保存处理器的中断等待状态。当处理器出现中断无法立即响应的情况时,MIP 寄存器中的对应位会被置位。
|
用于保存处理器的中断等待状态。当处理器出现中断无法立即响应的情况时, SIP 寄存器中的对应位会被置位。
|
1.1 scause
mcause/scause记录了M/S模式下异常类型:
- 当 Interrupt 位为 1 时,表示触发异常的来源是中断, Exception Code 按照中断解析。包括机器模式(软件中断、计时器中断、外部中断)、超级用户模式(软件中断、计时器中断、外部中断)、L1数据ECC中断、PMU中断。
- 当 Interrupt 位为 0 时,表示触发异常的来源不是中断, Exception Code 按照异常解析。
具体异常类型说明如下:
如果同时产生多个同步异常,由于mcause只能保存一个Exception Code,最终mcause会按照如下优先级选择高优先级的Exception Code。
1.2 stval
stval是Supervisor Trap Value寄存器,提供与当前异常scause相关的附加信息,包括:
-
when a breakpoint(3), address-misaligned(0), access-fault(1、5、7), or page-fault(12、13、15) exception occurs on an instruction fetch, load, or store, then stval will contain the faulting virtual address
-
when a misaligned load or store(4、6) causes an access-fault or page-fault exception, then stval will contain the virtual address of the portion of the access that caused the fault.
-
when an instruction access-fault or page-fault(12、13、15) exception occurs on a system with variable-length instructions, then stval will contain the virtual address of the portion of the instruction that caused the fault, while sepc will point to the beginning of the instruction.
- when an illegal-instruction(2) exception occurs, then stval will contain the shortest of:
- the actual faulting instruction
- the first ILEN bits of the faulting instruction
- the first SXLEN bits of the faulting instruction
-
For other traps, stval is set to zero, but a future standard may redefine stval’s setting for other traps.
1.3 stvec
stvec是Supervisor Trap-Vector基地址寄存器,由两部分组成:BASE和MODE。
- BASE是4字节对齐地址。
- MODE指定异常发生时pc指向何处,即使用哪个异常入口函数。
MODE=0时,所有的Traps处理都交给BASE指定的地址。
MODE=1时,所有同步异常处理交给BASE指定的地址;中断则指向BASE+irq*0x04的偏移值。比如Machine Timer Interrupt中断号为7,对应的中断处理函数指向BASE+0x1c。
1.4 sstatus
sstatus是Supervisor模式下状态寄存器:
详细解释如下:
- SD (State Dirty) [31]
- 脏状态标志:当 FS(浮点状态)或 XS(扩展状态)字段非零时置 1,表示浮点或扩展单元上下文需要保存。
- MXR (Make eXecutable Readable) [19]
- 可执行页面可读控制:允许将不可执行的虚拟内存页标记为可读(需支持虚拟内存扩展,如 Sv32)。
- SUM (permit Supervisor User Memory access) [18]
- 监督者模式用户内存访问权限:置 1 时允许监督者模式访问用户模式内存(需虚拟内存扩展支持)。
- XS (Extension State) [16:15]
- 扩展单元状态:管理自定义扩展模块(如加速器)的上下文状态(编码同 FS:00=关闭,01=初始,10=清理,11=活跃)。
- FS (Floating-point State) [14:13]
- 浮点单元状态:控制浮点寄存器上下文状态(00=关闭,01=初始,10=清理,11=活跃)。
- VS (Vector State) [10:9]
- 向量单元状态:管理向量扩展(如 RVV)的上下文状态(编码同 FS,需支持向量扩展)。
- SPP (Supervisor Previous Privilege) [8]
- 前特权级记录:记录进入监督者模式前的特权级(0=用户模式,1=监督者模式)。
- UBE (User-mode Byte Order) [6]
- 用户模式字节序配置:设置用户模式内存访问的字节序(0=小端,1=大端,需硬件支持)。
- SPIE (Supervisor Previous Interrupt Enable) [5]
- 前中断使能状态:保存进入监督者模式前的中断使能状态(1=中断开启,0=中断关闭)。
- SIE (Supervisor Interrupt Enable) [1]
- 监督者模式中断使能:控制监督者模式下的全局中断(1=启用中断,0=禁用中断)。
1.5 sip和sie
sip是Supervisor Interrupt Pending寄存器,记录Supervisor模式下哪些中断处于Pending的寄存器:
- SSIP (Supervisor Software Interrupt Pending) [1]
- 监督者模式软件中断挂起标志(1=挂起,需手动清除)。
- STIP (Supervisor Timer Interrupt Pending) [5]
- 监督者模式定时器中断挂起标志(1=挂起,由硬件置位/清除)。
- SEIP (Supervisor External Interrupt Pending) [9]
- 监督者模式外部中断挂起标志(1=挂起,由外部中断源触发)。
sie是Supervisor Interrupt Enable寄存器,控制Supervisor模式下中断使能:
- SSIE (Supervisor Software Interrupt Enable) [1]
- 监督者模式软件中断使能(1=启用,0=禁用)。
- STIE (Supervisor Timer Interrupt Enable) [5]
- 监督者模式定时器中断使能(1=启用,0=禁用)。
- SEIE (Supervisor External Interrupt Enable) [9]
- 监督者模式外部中断使能(1=启用,0=禁用)。
1.6 sscratch和sepc
sscratch是Supervisor模式下的临时存储寄存器,主要用于异常处理或上下文切换时的临时数据保存。
sepc是Supervisor模式下保存发生异常或中断时的程序计数器(PC)值,用于在异常处理完成后恢复原执行流程。
1.7 scounteren
scounteren控制用户模式下对特定硬件计数器的访问权限,1表示允许;0表示禁止。
1. CY (Cycle Counter Enable) [0]
用户模式 cycle 计数器访问使能:
- 1:允许用户程序读取 cycle 计数器(记录处理器周期数)。
- 0:禁止访问,读操作会触发非法指令异常。
2. TM (Time Counter Enable) [1]
用户模式 time 计数器访问使能:
- 1:允许用户程序读取 time 计数器(记录实时时钟值)。
- 0:禁止访问,读操作会触发非法指令异常。
3. IR (Instret Counter Enable) [2]
用户模式 instret 计数器访问使能:
- 1:允许用户程序读取 instret 计数器(记录已执行指令数)。
- 0:禁止访问,读操作会触发非法指令异常。
4. HPM (HPM Counter Enable) [3:31]
用户模式硬件性能计数器(HPM)访问使能:
- 每个位对应一个 HPM 计数器(如 hpmcounter3 对应位 3)。
- 1:允许用户程序读取对应 HPM 计数器。
- 0:禁止访问。
2 Linux下RISC-V异常处理
2.1 异常向量注册
_start
_start_kernel
setup_trap_vector
la a0, handle_exception
csrw CSR_TVEC, a0--设置Trap Vector为handle_exception函数。
2.2 异常处理入口函数
Linux下异常处理入口函数为:
SYM_CODE_START(handle_exception) /* * If coming from userspace, preserve the user thread pointer and load * the kernel thread pointer. If we came from the kernel, the scratch * register will contain 0, and we should continue on the current TP. */ csrrw tp, CSR_SCRATCH, tp bnez tp, .Lsave_context .Lrestore_kernel_tpsp: csrr tp, CSR_SCRATCH REG_S sp, TASK_TI_KERNEL_SP(tp) #ifdef CONFIG_VMAP_STACK addi sp, sp, -(PT_SIZE_ON_STACK) srli sp, sp, THREAD_SHIFT andi sp, sp, 0x1 bnez sp, handle_kernel_stack_overflow REG_L sp, TASK_TI_KERNEL_SP(tp) #endif .Lsave_context: REG_S sp, TASK_TI_USER_SP(tp) REG_L sp, TASK_TI_KERNEL_SP(tp) addi sp, sp, -(PT_SIZE_ON_STACK) REG_S x1, PT_RA(sp) REG_S x3, PT_GP(sp) REG_S x5, PT_T0(sp) save_from_x6_to_x31 /* * Disable user-mode memory access as it should only be set in the * actual user copy routines. * * Disable the FPU/Vector to detect illegal usage of floating point * or vector in kernel space. */ li t0, SR_SUM | SR_FS_VS--禁用用户模式内存访问和FPU/Vector。 REG_L s0, TASK_TI_USER_SP(tp) csrrc s1, CSR_STATUS, t0 csrr s2, CSR_EPC csrr s3, CSR_TVAL csrr s4, CSR_CAUSE csrr s5, CSR_SCRATCH REG_S s0, PT_SP(sp) REG_S s1, PT_STATUS(sp) REG_S s2, PT_EPC(sp) REG_S s3, PT_BADADDR(sp) REG_S s4, PT_CAUSE(sp) REG_S s5, PT_TP(sp) /* * Set the scratch register to 0, so that if a recursive exception * occurs, the exception vector knows it came from the kernel */ csrw CSR_SCRATCH, x0 /* Load the global pointer */ load_global_pointer /* Load the kernel shadow call stack pointer if coming from userspace */ scs_load_current_if_task_changed s5 #ifdef CONFIG_RISCV_ISA_V_PREEMPTIVE move a0, sp call riscv_v_context_nesting_start #endif move a0, sp /* pt_regs */ /* * MSB of cause differentiates between * interrupts and exceptions */ bge s4, zero, 1f--bge的s4是有符号数,根据scause寄存器如果是中断则MSB为1,即负值。所以大于等于0,则是异常。 /* Handle interrupts */ call do_irq--进行中断处理。 j ret_from_exception--返回。 1: /* Handle other exceptions */ slli t0, s4, RISCV_LGPTR--slli是逻辑左移指令。如果是异常,每个异常跳转指针为4字节,根据异常向量号,计算出异常跳转指针在异常向量表中偏移量。 la t1, excp_vect_table--异常向量表起始地址。 la t2, excp_vect_table_end--异常向量表结束地址。 add t0, t1, t0--得到异常对应的异常处理函数指针地址。 /* Check if exception code lies within bounds */ bgeu t0, t2, 3f--检查异常函数是否在范围内。 REG_L t1, 0(t0)--加载异常处理函数到寄存器t1。 2: jalr t1--跳转到异常函数处理。 j ret_from_exception--返回。 3: la t1, do_trap_unknown--处理无法识别的异常。 j 2b SYM_CODE_END(handle_exception)
在entry.S中定义了异常向量表:
SYM_CODE_START(excp_vect_table) RISCV_PTR do_trap_insn_misaligned ALT_INSN_FAULT(RISCV_PTR do_trap_insn_fault) RISCV_PTR do_trap_insn_illegal RISCV_PTR do_trap_break RISCV_PTR do_trap_load_misaligned RISCV_PTR do_trap_load_fault RISCV_PTR do_trap_store_misaligned RISCV_PTR do_trap_store_fault RISCV_PTR do_trap_ecall_u /* system call */ RISCV_PTR do_trap_ecall_s RISCV_PTR do_trap_unknown RISCV_PTR do_trap_ecall_m /* instruciton page fault */ ALT_PAGE_FAULT(RISCV_PTR do_page_fault) RISCV_PTR do_page_fault /* load page fault */ RISCV_PTR do_trap_unknown RISCV_PTR do_page_fault /* store page fault */ excp_vect_table_end: SYM_CODE_END(excp_vect_table)
2.3 不同类型异常处理
不同异常类型在Linux中处理:
异常向量号 | 描述 | 异常处理函数 | 异常信号 | 异常log | 异常原因和解决方法 |
0 | 执行未对齐指令异常 | do_trap_insn_misaligned | SIGBUS、BUS_ADRALN | instruction address misaligned |
原因:尝试执行一个未对齐的指令地址。 |
1 | 取指令访问错误异常 | do_trap_insn_fault | SIGSEGV、SEGV_ACCERR | instruction access fault |
原因:访问指令存储器时发生故障,可能是因为物理地址不合法或存储器保护错误。 |
2 | 非法指令异常 | do_trap_insn_illegal | SIGILL、ILL_ILLOPC |
原因:执行了一个未定义的指令。 |
|
3 | 调试断点异常 | do_trap_break |
原因:遇到了断点指令,通常用于调试。 |
||
4 | 加载指令非对齐访问异常 | do_trap_load_misaligned | SIGBUS、BUS_ADRALN | Oops - load address misaligned |
原因:尝试从未对齐的地址加载数据。 |
5 | 加载指令访问错误异常 | do_trap_load_fault | SIGSEGV、SEGV_ACCERR | load access fault |
原因:从存储器加载指令时发生故障,可能是因为物理地址不合法或存储器保护错误。 |
6 | 存储/原子指令非对齐访问异常 | do_trap_store_misaligned | SIGBUS、BUS_ADRALN | Oops - store (or AMO) address misaligned |
原因:尝试向未对齐的地址存储数据。 |
7 | 存储/原子指令访问错误异常 | do_trap_store_fault | SIGSEGV、SEGV_ACCERR | store (or AMO) access fault |
原因:向存储器存储数据时发生故障,可能是因为物理地址不合法或存储器保护错误。 |
8 | 用户模式环境调用异常 | do_trap_ecall_u | SIGILL、ILL_ILLTRP | environment call from U-mode |
原因:从用户模式发起的ecall。 |
9 | 超级用户模式环境调用异常 | do_trap_ecall_s | SIGILL、ILL_ILLTRP | environment call from S-mode |
原因:从超级用户模式发起的ecall。 |
10 | 保留 | do_trap_unknown | SIGILL、ILL_ILLTRP | unknown exception |
|
11 | 机器模式环境调用异常 | do_trap_ecall_m | SIGILL、ILL_ILLTRP | environment call from M-mode |
|
12 | 取指页面错误异常 | do_page_fault |
原因:从虚拟地址到物理地址的指令转换失败。 |
||
13 | 加载指令页面错误异常 | do_page_fault |
原因:从虚拟地址到物理地址的指令加载转换失败。 |
||
14 | 保留 | do_trap_unknown | SIGILL、ILL_ILLTRP | unknown exception | |
15 | 存储/原子指令页面错误异常 | do_page_fault |
原因:向虚拟地址写入数据时页转换失败。 |
SYM_DATA_START_LOCAL(excp_vect_table) RISCV_PTR do_trap_insn_misaligned ALT_INSN_FAULT(RISCV_PTR do_trap_insn_fault) RISCV_PTR do_trap_insn_illegal RISCV_PTR do_trap_break RISCV_PTR do_trap_load_misaligned RISCV_PTR do_trap_load_fault RISCV_PTR do_trap_store_misaligned RISCV_PTR do_trap_store_fault RISCV_PTR do_trap_ecall_u /* system call */ RISCV_PTR do_trap_ecall_s RISCV_PTR do_trap_unknown RISCV_PTR do_trap_ecall_m /* instruciton page fault */ ALT_PAGE_FAULT(RISCV_PTR do_page_fault) RISCV_PTR do_page_fault /* load page fault */ RISCV_PTR do_trap_unknown RISCV_PTR do_page_fault /* store page fault */ SYM_DATA_END_LABEL(excp_vect_table, SYM_L_LOCAL, excp_vect_table_end)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)