ARMv8-AArch64 的异常处理模型详解之异常类型 Exception types

发布时间:2024年01月20日

一, 什么是异常

异常(Exception)通俗点来讲,就是系统在正常运行的时候出现的非正常事件,这个非正常事件会导致系统状态更改或者其他错误,为了确保系统功能能正常运行,需要一些带有特权的软件代码(exception handler)来采取一些补救措施或者更新系统状态,这个过程被称为异常处理,系统会停止当前程序,并进入handler,在handler处理完成之后,系统会从handler返回,再继续正常运行。
所以可以说,异常就是可以导致当前正常程序被挂起的任何事件。
如下图所示,程序正常执行过程中,发生了异常,程序挂起,并跳转到异常处理函数,在处理完异常后,从异常返回,程序恢复正常运行:
在这里插入图片描述
有些处理器架构可能会讲上述过程描述为一个中断,但是在ARM AArch64中,中断是一种由外部产生的,特殊类型的异常。

异常的用途非常广泛,包括以下:

  • 模拟虚拟设备
  • 虚拟内存管理
  • 处理软件错误
  • 处理硬件错误
  • 调试
  • 异常等级跳转或者安全状态切换
  • 处理中断(定时器或者外设间的交互)
  • 执行状态切换(AArch64和AArch32),也就是interprocessing

当异常发生时,处理器将会停止当前程序的运行,跳转到一段称为异常处理函数的代码来处理该异常,处理结束后将从exception handler返回到之前程序停止的地方继续执行。每一种异常类型都有属于自己的异常处理函数。此外,ARM架构将异常分为同步异常(synchronous exceptions)和异步异常(asynchronous exceptions)。

二,同步异常(synchronous exceptions)

同步异常用一句话概括就是:由处理器执行指令所产生的异常。同步异常和当前处理器所执行的指令流是同步的,因为正是处理器执行了某条指令才导致异常发生。比如,MMU定义了某个内存区间为只读属性,如果CPU对该区间某地址执行了写的指令(如STR),此时一个同步异常将会发生。
同步异常有如下特点:

  • 同步异常是由于直接或试图执行指令而产生的。
  • 处理异常后返回的地址(返回地址)与导致异常的指令之间的关系,是由arm体系结构定义的。
  • 同步异常也称为 精准(precise)异常,因为我们可以知道具体是哪条指令导致的异常,可以精准地知道执行了哪条指令之后系统状态才开始发生变化,并且进入异常处理函数。
    同步异常也分了很多种,并且也可能存在 执行一条指令,导致多个同步异常的产生的情况,为了处理多个同步异常同时产生的情况,ARM架构为每种同步异常规定了固定的优先级来处理。

2.1 无效的指令和陷阱异常(Invalid instructions and trap exceptions)

无效的指令有很多种情况,比如:

  • Cortex-A73支持的某些指令或者寄存器,A53不支持,对于A53来讲就是无效的指令,如果执行这样的指令或者访问不支持的寄存器,处理器将进入 未定义的异常(UNDEFINED Exception)。
  • 有些指令或者寄存器只针对特定的异常等级有效,如果在其他的异常等级下执行这样的指令或者访问这些寄存器,也将进入未定义的异常(UNDEFINED Exception)。
    此外,ARM架构还允许操作系统或者hypervisor为更低异常等级的某些操作(比如读取一个特定的寄存器)设置陷阱。当处理器在这些更低异常等级执行这些操作的时候,将会触发一个异常。
    例如,EL1上的操作系统内核可能会在EL0上禁用使用浮点指令,以节省在应用程序之间进行上下文切换时的时间(浮点寄存器通常有128-bit的宽度,是普通通用寄存器的两倍,上下文切换时,需要将所有GPR的值压入栈或者从栈中恢复到GPR)。这被称为惰性上下文切换;例如,如果在上下文切换之前没有使用SIMD/浮点(FP)单元(NEON),则出栈入栈的寄存器数量可以减少。所以,一般情况下,在应用程序跑的EL0异常等级下,禁用浮点指令,并且使用一个陷阱异常来处理边缘情况(少数情况下,某些应用程序需要开启浮点运算)。
    在这种情况下,OS内核可以通过禁用SIMD/FP单元来监视SIMD/FP操作的状态。当执行FP或SIMD指令时,陷阱异常被带到EL1的OS内核。然后,内核可以启用SIMD/FP单元,以执行之前失败的指令,并设置一个标志,说明已经使能了SIMD/FP单元。这确保了在下一个上下文交换中,将大型SIMD/FP寄存器文件包含在上下文切换的寄存器上下文中。如果在下一个上下文切换中没有发现这个使能标志,则不需要包含SIMD/FP寄存器。
    这种设置陷阱异常的能力对于虚拟化尤为重要。关于AArch64 的虚拟化操作,可以参考:AArch64 virtualization guide

2.2 内存访问产生的异常

在访问内存时也会产生同步异常。可能的原因有:

  • MMU使能后,MMU进行地址转换时进行检查产生的。
  • 由内存系统(比如DDR)返回的错误。
    比如,将MMU使能后,使用Load或者store指令访问内存的操作将会受到MMU的检查。比如对一些地址空间的内存属性设置了只读,这时候CPU对该区域进行写操作,还比如对一些地址空间设置了只有较高的异常等级才能去访问(即该区域具有特权),此时若CPU处于非特权状态去访问该区域,MMU对这些操作都会进行检查,并将这些操作拦截,然后触发一个MMU 错误的异常。由于MMU产生的错误是同步的,所以该异常的产生将会在内存访问之前。
    此外,内存访问也同样会产生异步异常,比如SEerror。这个将会在下文中描述。
    在AArch64中,同步的Data abort(数据访问中止)将会产生一个同步异常,如果是异步的abort,将会产生一个SError中断异常。

2.3 产生异常的指令

ARM架构提供了一些显式的指令,用于产生一个异常。这些指令用于实现系统调用接口,以允许低特权的软件向高特权的软件请求服务。这些方法有时被称为系统调用,通常用于基于软件的API。
ARM架构包含三个异常产生的指令:SVC,HVC以及SMC。这三个指令仅仅是用于产生一个异常,并以此让处理器在各个异常等级中切换:

  • Supervisor Call (SVC),SVC指令用于EL0切换到EL1,比如一个工作在EL0的用户程序,可以使用SVC指令,请求一个工作在EL1操作系统的服务。

  • Hypervisor Call (HVC),如果虚拟化在当前架构中被实现,比如在虚拟机程序中,工作在EL1的操作系统可以通过HVC指令,向工作在EL2的hypervisor请求服务,可以进行不同的OS间的切换。

  • Secure Monitor Call (SMC),如果安全扩展功能在当前架构中被实现,则程序如果想要在安全世界(secure world)和非安全世界(normal world,non-secure world)切换,需要通过使用SMC指令来实现。

  • 此外,异常等级切换(低等级向高等级切换)需要遵守如下图规则:
    这些指令
    如果当前处理器处于EL0,则它不能直接使用HVC或者SMC,切换到更高等级。只能使用HVC指令先进入EL1,才可以切换到其他更高的异常等级。
    由于异常无法向更低等级切换,换句话说,如果是由高等级切到低等级,而是使用ERET指令。总结如下:

  • 系统调用(System Call),切换(SVC,HVC,SMC)到更高等级。

  • 返回(ERET,exception return)到更低等级。

所以,如果处于EL2的处理器执行SVC指令是不会切到EL1的。

2.4 调试异常 Debug exceptions

调试异常也是同步异常,该异常会被路由到当前调试器所处的异常等级。然后,调试器代码执行得很像异常处理程序代码一样。调试异常有很多种,包括如下:

  • Breakpoint Instruction exceptions
  • Breakpoint exceptions
  • Watchpoint exceptions
  • Vector Catch exceptions
  • Software Step exceptions
    关于调试异常的具体介绍,可以查看AArch64 self-hosted debug guide
文章来源:https://blog.csdn.net/luolaihua2018/article/details/135568107
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。