前言
本文章的目的仅为记录学习过程中的学习心得,不作为任何参考依据。
异常处理
当arm产生异常时,会跳转到对应的向量表中去执行,在linux下,以4.9版本为例,异常向量的入口在arch/arm64/kernel/entry.S 中
/*
* Exception vectors.
*/
.pushsection ".entry.text", "ax"
.align 11
ENTRY(vectors)
ventry el1_sync_invalid // Synchronous EL1t
ventry el1_irq_invalid // IRQ EL1t
ventry el1_fiq_invalid // FIQ EL1t
ventry el1_error_invalid // Error EL1t
ventry el1_sync // Synchronous EL1h
ventry el1_irq // IRQ EL1h
ventry el1_fiq_invalid // FIQ EL1h
ventry el1_error_invalid // Error EL1h
ventry el0_sync // Synchronous 64-bit EL0
ventry el0_irq // IRQ 64-bit EL0
ventry el0_fiq_invalid // FIQ 64-bit EL0
ventry el0_error_invalid // Error 64-bit EL0
#ifdef CONFIG_COMPAT
ventry el0_sync_compat // Synchronous 32-bit EL0
ventry el0_irq_compat // IRQ 32-bit EL0
ventry el0_fiq_invalid_compat // FIQ 32-bit EL0
ventry el0_error_invalid_compat // Error 32-bit EL0
#else
ventry el0_sync_invalid // Synchronous 32-bit EL0
ventry el0_irq_invalid // IRQ 32-bit EL0
ventry el0_fiq_invalid // FIQ 32-bit EL0
ventry el0_error_invalid // Error 32-bit EL0
#endif
END(vectors)
其中,ventry被定义为
.macro ventry label
.align 7
b \label
.endm
这是一段宏定义,意思是以不返回的形式跳转到label表示处。
这个表存在具体什么地址中呢,对于ARM V8架构来说(arm V7不是这个形式),下面是其异常向量表
从这张表我们可以知道,这些向量被分为两组,每一组又被分为两个子组:
异常等级有变化(准确来说是异常等级提高,从低等级进入到更高等级,Exception from Lower EL)
- 低等级是AArch32状态
- 低等级是AArch64状态
异常等级无变化
- 异常使用SP_ELx。
- 异常使用SP_EL0。
在linux下,内核态下产生中断,偏移地址为0x280;用户态下,偏移地址为0x480
这个表的基地址可以来设置寄存器VBAR_ELx(在linux中通常是VBAR_EL1)。
读写方式为
MRS <Xt>, VBAR_EL1
MSR VBAR_EL1, <Xt>
当内核中产生中断时,由于在ARM V8架构上,用户态运行在el0等级,此时产生中断,会陷入到内核态,即el1等级,因此其对应会执行el0_irq 函数
.align 6
el0_irq:
kernel_entry 0
el0_irq_naked:
enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
ct_user_exit
irq_handler
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
b ret_to_user
ENDPROC(el0_irq)
代码最终会执行irq_handler处理函数,来处理用户态中断。ret_to_user用于返回用户态(前面讲到,发生中断后,从用户态切到了内核态)。再来看irq_handler函数
.macro irq_handler
ldr_l x1, handle_arch_irq
mov x0, sp
irq_stack_entry
blr x1
irq_stack_exit
.endm
这也是一个宏定义,调用handle_arch_irq,handle_arch_irq在\arch\arm64\kernel\irq.c文件中被赋值。
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return;
handle_arch_irq = handle_irq;
}
显而易见,该函数是在linux启动过程中只想特定的函数地址的这也符合linux的特点,任何驱动都可以通过指针去定义。后面的初始化以及中断注册,留给后面慢慢讲。