【ARMv8基础篇】异常分类以及异常处理机制

前文:

【ARMv8基础篇】异常等级
【ARMv8基础篇】寄存器
【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)的寄存器中获取状态信息。

【ARMv8基础篇】异常分类以及异常处理机制

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

【ARMv8基础篇】异常分类以及异常处理机制

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

【ARMv8基础篇】异常分类以及异常处理机制

同样,从管理程序代码(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的向量表如下图所示,可以看到每一种异常都有固定的偏移地址。

【ARMv8基础篇】异常分类以及异常处理机制

【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。

继续挖坑。。。。。

欢迎关注我的个人微信公众号,一起交流学习嵌入式开发知识!
关注「求密勒实验室」

Original: https://blog.csdn.net/qq_38131812/article/details/124597495
Author: 漫游嵌入式
Title: 【ARMv8基础篇】异常分类以及异常处理机制

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/662488/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球