异常初始化
异常处理概述
略
异常入口
start_kernel
trap_init();
void __init trap_init(void)
{
/*
* Set sup0 scratch register to 0, indicating to exception vector
* that we are presently executing in the kernel
*/
csr_write(CSR_SSCRATCH, 0);
/* Set the exception vector address */
csr_write(CSR_STVEC, &handle_exception);
/* Enable all interrupts */
csr_write(CSR_SIE, -1);
设置trap的入口函数为handle_exception。
}
异常软件处理流程
ENTRY(handle_exception)
SAVE_ALL
① 保存上下文,主要是一些寄存器
/*
* Set sscratch register to 0, so that if a recursive exception
* occurs, the exception vector knows it came from the kernel
*/
csrw CSR_SSCRATCH, x0
②清空临时寄存器scratch
/* Load the global pointer */
.option push
.option norelax
la gp, __global_pointer$
.option pop
la ra, ret_from_exception
③ 设置异常返回
/*
* MSB of cause differentiates between
* interrupts and exceptions
*/
bge s4, zero, 1f
/* Handle interrupts */
move a0, sp /* pt_regs */
tail do_IRQ
④s4里面保存的是scause的值,通过最高位判断是中断还是异常,中断调用do_IRQ,异常的就接着往下运行。关于do_IRQ的处理待后续中断处理流程单独分析。
1:
/* Exceptions run with interrupts enabled or disabled
depending on the state of sstatus.SR_SPIE */
andi t0, s1, SR_SPIE
beqz t0, 1f
csrs CSR_SSTATUS, SR_SIE
1:
/* Handle syscalls */
li t0, EXC_SYSCALL
beq s4, t0, handle_syscall
④ 异常类型是系统调用,则跳转handle_syscall
/* Handle other exceptions */
slli t0, s4, RISCV_LGPTR
la t1, excp_vect_table
la t2, excp_vect_table_end
move a0, sp /* pt_regs */
add t0, t1, t0
/* Check if exception code lies within bounds */
bgeu t0, t2, 1f
REG_L t0, 0(t0)
jr t0
⑤如果不是系统调用,处理其他类型异常,根据excp_vect_table进行跳转,如do_page_fault
1:
tail do_trap_unknown
⑥如果从excp_vect_table都未查询到对应的异常,进行单独处理。
handle_syscall:
/* save the initial A0 value (needed in signal handlers) */
REG_S a0, PT_ORIG_A0(sp)
/*
* Advance SEPC to avoid executing the original
* scall instruction on sret
*/
addi s2, s2, 0x4
REG_S s2, PT_SEPC(sp)
/* Trace syscalls, but only if requested by the user. */
REG_L t0, TASK_TI_FLAGS(tp)
andi t0, t0, _TIF_SYSCALL_WORK
bnez t0, handle_syscall_trace_enter
check_syscall_nr:
/* Check to make sure we don't jump to a bogus syscall number. */
li t0, __NR_syscalls
la s0, sys_ni_syscall
/* Syscall number held in a7 */
bgeu a7, t0, 1f
la s0, sys_call_table
slli t0, a7, RISCV_LGPTR
add s0, s0, t0
REG_L s0, 0(s0)
1:
jalr s0
ret_from_syscall:
/* Set user a0 to kernel a0 */
REG_S a0, PT_A0(sp)
/* Trace syscalls, but only if requested by the user. */
REG_L t0, TASK_TI_FLAGS(tp)
andi t0, t0, _TIF_SYSCALL_WORK
bnez t0, handle_syscall_trace_exit
⑦上面是系统调用的异常处理,后续再单独分析。
ret_from_exception:
REG_L s0, PT_SSTATUS(sp)
csrc CSR_SSTATUS, SR_SIE
andi s0, s0, SR_SPP
bnez s0, resume_kernel
⑧从异常中返回,根据sstatus中SPP的位来判断是内核陷入的异常还是用户陷入的异常,0是用户陷入的异常,跳转执行resume_userspace,1是内核陷入的异常,跳转执行resume_kernel。sstatus[8]是SPP位,保存的是陷入异常前是U模式还是S模式,如果是U模式代表是用户态,如果是S模式代表是内核态。
resume_userspace:
/* Interrupts must be disabled here so flags are checked atomically */
REG_L s0, TASK_TI_FLAGS(tp) /* current_thread_info->flags */
andi s1, s0, _TIF_WORK_MASK
bnez s1, work_pending
/* Save unwound kernel stack pointer in thread_info */
addi s0, sp, PT_SIZE_ON_STACK
REG_S s0, TASK_TI_KERNEL_SP(tp)
/*
* Save TP into sscratch, so we can find the kernel data structures
* again.
*/
csrw CSR_SSCRATCH, tp
⑨ 恢复用户态的tp,线程指针,待到后续进程调度时再详细分析。
restore_all:
RESTORE_ALL
sret
⑩ 恢复上下文,执行sret返回。
arch/riscv/kernel/entry.S是关于risc-v trap入口的实现,trap的包含异常和中断。trap的中总入口为handle_exception,主要处理一下事项。
– 保存上下文,主要是将一些寄存器压入栈中。
– 根据scause寄存器判断是异常还是中断,如果是中断跳转到do_IRQ运行,如果是异常根据异常的cause进分发处理。
– 异常中有一类是系统调用,根据scause查询如果是系统调用根据sys_call_table[]跳转处理。
– 异常如果不是系统调用,就是程序运行中出现的异常,根据scause查询excp_vect_table[]跳转处理。
– 异常和中断都处理完成之后,最终回到ret_from_exception,这里会根据sstatus的SSP位来判断是用户态陷入的还是内核态陷入,根据内核和用户态的更新tp。
– 最后调用RESTORE_ALL恢复上下文,退出trap。