RISC-V:异常及其在Linux下的处理
首先了解RISC-V异常相关配置和寄存器,然后了解各种异常类型以及Linux下是如何处理的。
1 RISC-V异常类型
机器模式
|
超级用户模式
|
||||
异常配置寄存器组
|
处理器状态寄存器
|
MSTATUS
|
存储了处理器在机器模式下的状态和控制信息,包括全局中断有效位、异常保留中断有效位、异常保留特权模式位等。
|
SSTATUS
|
存储了处理器在超级用户模式下的状态和控制信息,包括全局中断有效位、异常保留中断有效位、异常保留特权模式位等,是 MSTATUS 的部分映射。
|
处理器指令集特性寄存器
|
MISA
|
存储了处理器所支持的指令集架构特性。
|
|||
异常降级控制寄存器
|
MEDELEG
|
可以将超级用户和用户模式发生的异常降级到超级用户模式响应。
|
|||
中断降级控制寄存器
|
MIDELEG
|
可以将超级用户模式中断降级到超级用户模式响应。
|
|||
中断使能控制寄存器
|
MIE
|
用于控制不同中断类型的使能和屏蔽。
|
SIE
|
用于控制不同中断类型的使能和屏蔽,是 MIE 的部分映射。
|
|
向量基址寄存器
|
MTVEC
|
用于配置异常服务程序的入口地址。
|
STVEC
|
用于配置异常服务程序的入口地址。
|
|
计数器访问授权寄存器
|
MCOUNTEREN
|
用于授权超级用户模式是否可以访问用户模式计数器。
|
SCOUNTEREN
|
用于授权用户模式是否可以访问用户模式计数器。
|
|
异常处理寄存器组
|
异常临时数据备份寄存器
|
MSCRATCH
|
用于处理器在异常服务程序中备份临时数据。一般用来存储机器模式本地上下文空间的入口指针值。
|
SSCRATCH
|
用于处理器在异常服务程序中备份临时数据。一般用来存储超级用户模式本地上下文空间的入口指针值。
|
异常保留程序计数器寄存器
|
MEPC
|
用于存储程序从异常服务程序退出时的程序计数器值(即 PC 值)。
|
SEPC
|
用于存储程序从异常服务程序退出时的程序计数器值(即 PC 值)
|
|
异常事件向量寄存器
|
MCAUSE
|
用于保存触发异常的异常事件向量号,用于在异常服务程序中处理对应事件。
|
SCAUSE
|
用于保存触发异常的异常事件向量号,用于在异常服务程序中处理对应事件。
|
|
中断等待状态寄存器
|
MIP
|
用于保存处理器的中断等待状态。当处理器出现中断无法立即响应的情况时,MIP 寄存器中的对应位会被置位。
|
SIP
|
用于保存处理器的中断等待状态。当处理器出现中断无法立即响应的情况时, SIP 寄存器中的对应位会被置位。
|
1.1 scause
mcause/scause记录了M/S模式下异常类型:
- 当 Interrupt 位为 1 时,表示触发异常的来源是中断, Exception Code 按照中断解析。包括机器模式(软件中断、计时器中断、外部中断)、超级用户模式(软件中断、计时器中断、外部中断)、L1数据ECC中断、PMU中断。
- 当 Interrupt 位为 0 时,表示触发异常的来源不是中断, Exception Code 按照异常解析。
具体异常类型说明如下:
1.2 stvec
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)
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)