ARMv8中的异常及其处理机制

举报
Qmiller 发表于 2022/05/06 11:32:00 2022/05/06
【摘要】 在ARM64中,除了中止、复位和软件异常外,中断也是属于异常的一种。下面我们就来了解一下异常种类、异常处理和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)的寄存器中获取状态信息。

image-20220505200020070

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()请求内存。

image-20220505200431237

对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是未分配的,并在当前的异常级别触发。

image-20220505200647813

同样,从管理程序代码(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_EL3VBAR_EL2VBAR_EL1

每个表有16个条目,每个条目的大小为128字节(32条指令)。ARMv8的向量表如下图所示,可以看到每一种异常都有固定的偏移地址。

image-20220505203414950 image-20220505203429732

下面是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。

继续挖坑。。。。。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。