ARMv8中的异常及其处理机制
在ARM64中,除了中止、复位和软件异常外,中断也是属于异常的一种。下面我们就来了解一下异常种类、异常处理和ARMv8的异常向量表。
异常种类
1、中断
在ARM中,FIQ的优先级要高于IRQ,在SOC内部会有一个中断控制器负责中断优先级调度,然后发送中断信号给处理器。中断属于异步模式的异常。
2、中止
中止异常分为数据中止和指令中止,MMU能够捕获错误并汇报给处理器。
3、复位
复位是处理器中优先级最高的异常,通常分为上电复位和软件复位。
4、软件产生的异常
ARMv8提供了3中软件产生的异常,发生此异常的原因是软件企图进入更高的异常等级。
- SVC 允许用户模式下的程序请求os服务
- HVC 允许客户机(Linux os)请求主机服务
- SMC 允许普通世界的程序请求安全服务
同步异常和异步异常
故名思议,同步异常必须等待cpu处理完当前异常才可以继续执行指令。
常见的同步异常:
- 访问其他等级的寄存器,比如当前是EL1,如果访问EL2的寄存器就会出现异常
- SP未对齐
- SVC、HVC和SMC
- 地址翻译错误/地址权限
常见的异步异常:
- 物理中断 IRQ、FIQ和系统错误
- 虚拟中断 vIRQ、vFIQ、vSError(埋个点吧,后续了解一下)
异常后的处理
**异常综合寄存器(ESR_ELn)和故障地址寄存器(FAR_ELn)**是为异常处理程序提供关于同步异常原因的信息。
ESR_ELn保存了异常原因,而FAR_ELn保存了所有同步指令和数据中止以及对齐故障的故障虚拟地址。
**异常链接寄存器(ELR_ELn)**也保存了导致中止数据访问的指令的地址(对于数据中止)。这些在内存故障后被更新,但在其他情况下也会被设置,例如,通过分支到错位的地址。如果一个异常从AArch32中的异常级别进入使用AArch64的异常级别,并且该异常写入了与目标异常级别相关的故障地址寄存器,FAR_ELn的前32位都被设置为0。
异常综合寄存器,ESR_ELn,包含了允许异常处理程序确定异常原因的信息。它只对同步异常和SError进行更新。它不对IRQ或FIQ进行更新,因为这些中断处理程序通常从通用中断控制器(GIC)的寄存器中获取状态信息。
Bit[31:26](ESR_ELn.EC)表示异常类别,它使处理程序能够区分各种可能的异常原因(如未分配的指令,从MCR或MRC到CP15的异常,FP操作的异常,SVC、HVC或SMC的执行,数据中止,以及对齐异常)。例如,EC=101111是一个SError中断。
Bit[25](ESR_ELn.IL)表示被困指令的长度(16位指令为0,32位指令为1),并对某些异常类别进行设置。
Bit[24:0] (ESR_ELn.ISS)构成指令特定综合症(ISS)字段,包含该异常类型的特定信息。例如,当一个系统调用指令(SVC、HVC或SMC)被执行时,该字段包含与操作码相关的即时值,如SVC为0x123456。
系统调用
一些指令或系统功能只能在特定的Exception级别进行。例如,如果运行在较低Exception级别的代码必须执行一个特权操作,例如当应用程序代码向内核请求功能时。一种方法是通过使用SVC指令来实现。这允许应用程序产生一个异常。参数可以在寄存器中传递,或者在系统调用中编码。
例如:EL0的应用程序代码使用malloc()请求内存。
对EL2/EL3的系统调用
SVC指令可以用来从EL0的用户应用程序调用到EL1的内核。HVC和SMC系统调用指令以类似的方式将处理器移动到EL2和EL3。当处理器在EL0(应用程序)执行时,它不能直接调用管理程序(EL2)或安全监视器(EL3)。这只有在EL1和以上的地方才有可能。因此,应用程序必须使用SVC来调用内核,并允许内核代表它们调用更高的异常级别。
在操作系统内核(EL1),软件可以用HVC指令调用管理程序(EL2),或用SMC指令调用安全监视器(EL3)。如果处理器是用EL3实现的,就可以提供让EL2从EL1捕获SMC指令的能力。如果没有EL3,SMC是未分配的,并在当前的异常级别触发。
同样,从管理程序代码(EL2)中,程序可以用SMC指令调用安全监视器(EL3)。如果你在EL2或EL3时进行SMC调用,它仍然会在同一个异常级别上引起一个同步异常,该异常级别的处理程序可以决定如何响应。
处理异常的流程
ARMv8的四个异常级别只能通过异常获取/返回的方式在异常等级之间跳转。
- 将PSTATE寄存器内容保存到对应等级的SPSR_ELx中(保存PSTATE现场)
- 保存返回地址到对应等级的ELR_ELx寄存器中(保存返回地址)
- 将PSTATE中的DAIF设1,即关闭调试异常、SError、IRQ和FIQ
- 设置对应异常等级下的栈指针,自动切换SP到SP_ELx
- 切换到对应目标异常等级,跳转到异常向量表执行
以上都是处理器自动完成的,OS所需要做的事就是从中断向量表开始,根据发生的异常类型,跳转到合适的异常向量。
当异常处理完后,执行eret指令从异常返回。eret会从ELR_ELx中恢复PC指针并且从SPSR_ELx中恢复到PSTATE中。
异常向量表
每个异常等级都有自己的异常向量表。异常向量表中的每一项都会保存有异常处理的跳转函数,然后跳转过去处理异常。
每个向量表基虚拟地址是由矢量基址寄存器设置的,例如VBAR_EL3,VBAR_EL2和VBAR_EL1。
每个表有16个条目,每个条目的大小为128字节(32条指令)。ARMv8的向量表如下图所示,可以看到每一种异常都有固定的偏移地址。
下面是Linux中的异常向量表,可以看到和上图一致。
SYM_CODE_START(vectors)
#Current EL with SP0
kernel_ventry 1, sync_invalid // Synchronous EL1t
kernel_ventry 1, irq_invalid // IRQ EL1t
kernel_ventry 1, fiq_invalid // FIQ EL1t
kernel_ventry 1, error_invalid // Error EL1t
#Current EL with SPx
kernel_ventry 1, sync // Synchronous EL1h
kernel_ventry 1, irq // IRQ EL1h
kernel_ventry 1, fiq_invalid // FIQ EL1h
kernel_ventry 1, error // Error EL1h
#Lower EL using A Arch64
kernel_ventry 0, sync // Synchronous 64-bit EL0
kernel_ventry 0, irq // IRQ 64-bit EL0
kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
kernel_ventry 0, error // Error 64-bit EL0
#ifdef CONFIG_COMPAT
#Lower EL using AArch32
kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
#else
kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
#endif
SYM_CODE_END(vectors)
kernel_ventry是一个汇编宏,参数1是异常等级,参数2是异常的标签。kernel_ventry 1, irq
这条最后的意思是跳转(b)到el1_irq函数去处理EL1中的IRQ异常。
.macro kernel_ventry, el, label, regsize = 64
.align 7
sub sp, sp, #S_FRAME_SIZE
b el\()\el\()_\label
对于el1_irq函数,其中的异常处理函数是handle_arch_irq,对于使用GICv3的ARM芯片来说,此函数是在drivers/irqchip/irq-gic-v3.c中使用set_handle_irq函数设置的。(埋点,深入了解arch/arm64/kernel/entry.S中的中断处理函数实现原理)
SYM_CODE_START_LOCAL_NOALIGN(el1_irq)
kernel_entry 1
el1_interrupt_handler handle_arch_irq
kernel_exit 1
SYM_CODE_END(el1_irq)
而对于el1级别的sync异常来说,中断处理函数是el1_sync_handler。
继续挖坑。。。。。
- 点赞
- 收藏
- 关注作者
评论(0)