温暖的电波  

Linux-5.4


结构struct pt_regs用以在堆栈中保存异常发生时的现场寄存器信息,其具体定义与cpu架构相关;内核发生异常时输出的debug信息就是通过show_regs(regs)来打印的(实际上并步严谨,有些上下文中可能无法获取到pt_regs时使用dump_stack())。
下面以arm64为背景进行介绍。

系统发生异常时会根据异常类型核异常级别进入到对应的异常向量入口,然后开始进行异常现场的保存:

1 kernel_ventry

sub     sp, sp, #S_FRAME_SIZE

在堆栈中预留出S_FRAME_SIZE大小的空间,S_FRAME_SIZE定义在arch/arm64/kernel/asm-offsets.c文件中,大小为sizeof(struct pt_regs);这里就是在堆栈中预留出sizeof(struct pt_regs)大小空间,预留后堆栈指针sp指向这个pt_reg的起始地址。

2 kernel_entry

stp     x0, x1, [sp, #16 * 0]
.......
stp     x28, x29, [sp, #16 * 14]
...
/* 如果异常发生在用户态(EL0)则pt_regs将保存用户态堆栈指针sp_el0 */
.if     \el == 0
mrs     x21, sp_el0
.else
/* 否则,异常发生在>EL0的异常级别,则pt_regs将保存上一次堆栈指针的起始位置 */
add     x21, sp, #S_FRAME_SIZE
.endif 

mrs     x22, elr_el1
mrs     x23, spsr_el1
stp     lr, x21, [sp, #S_LR]        //将异常发生前的链接指针lr和堆栈指针sp存放到堆栈中

.if \el == 0
stp     xzr, xzr, [sp, #S_STACKFRAME]
.else
stp     x29, x22, [sp, #S_STACKFRAME]        //对于EL != 0时,pt_regs.stackframe[]存放FP和异常LR
.endif
add     x29, sp, #S_STACKFRAME
stp     x22, x23, [sp, #S_PC]    //将异常链接地址(即异常处理完毕后的返回地址)elr_el1和异常发生前PE状态存保存在堆栈中

在kernel_entry这个宏中会将该次异常发生前的x0~x29寄存器、sp、lr、elr_el1、spsr_el1等等寄存器存放到堆栈的struct pt_regs内存中。

3 异常处理具体流程(以中断处理为例,el0_irq 或者el1_irq)

//irq_handler宏
ldr_l   x1, handle_arch_irq        //irq处理函数钩子,对于gic-v3而言是gic_handle_irq(struct pt_regs *regs)
mov     x0, sp            //用sp作为irq处理函数handle_arch_irq()的第一个参数
blr     x1

下面是更进一步调用关系:

gic_handle_irq(struct pt_regs *regs)
handle_domain_irq(gic_data.domain, irqnr, regs);
    set_irq_regs(regs)
        __this_cpu_write(__irq_regs, new_regs);

其中__irq_regs是percpu变量,专门用于存放中断发生时的现场堆栈指针

/*
 * Per-cpu current frame pointer - the location of the last exception frame on
 * the stack
 */
DECLARE_PER_CPU(struct pt_regs *, __irq_regs);

 最终, 各个cpu上的中断异常发生时的现场就存放在percpu __irq_regs中,通过get_irq_regs()函数可获取到。

 

posted on 2021-06-22 22:35  温暖的电波  阅读(2932)  评论(0编辑  收藏  举报