10_异常与中断

Posted 韦东山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10_异常与中断相关的知识,希望对你有一定的参考价值。

第十章 异常与中断

参考资料:

  • ARM® Cortex™-A Series Programmer’s Guide version4.0
  • ARM® Architecture Reference Manual ARMv7-A and ARMv7-R edition
  • Cortex™-A7 MPCore™Technical Reference Manual Revision: r0p5
  • ARM® Generic Interrupt Controller Architecture Specification Architecture version 2.0
  • 芯片手册《Chapter 3 & Chapter28 : Ultra Secured Digital Host Controller (uSDHC)》。

本章处理器架构的内容主要来自于ARM® Cortex™-A Series Programmer’s Guide version4.0。

10.1 ARM处理器模式和寄存器

​ ARM体系结构是一种基于模式的体系结构。在引入安全扩展之前,它具有7种处理器模式,如上表所示。有六个特权模式和一个非特权用户模式。特权是执行用户(非特权)模式无法完成的某些任务的能力。在用户模式下,对影响整个系统配置的操作存在一些限制,例如,MMU的配置和缓存操作。模式与异常事件相关。

​ TrustZone安全性扩展的引入了两个独立于特权模式和处理器模式的安全状态,并加入监控器模式(monitor mode)进行安全状态和非安全状态的切换。如下图所示:

​ 对于当前实现TrustZone扩展的处理器,系统安全是通过划分设备的所有硬件和软件资源属于安全世界或者非安全世界来实现的。处理器处于非安全状态时,它无法访问为安全状态分配的内存。

​ 在这种情况下,安全监控器充当在这两个世界之间切换的网关。如果实施了安全扩展,则在监视器模式下执行的软件将控制安全和不安全处理器状态之间的转换。

​ ARMv7-A体系结构虚拟化扩展,在现有的特权模式外,还添加了hypervisor mode(Hyp)。虚拟化使多个操作系统可以共存并在同一系统上运行。因此,ARM虚拟化扩展使在同一平台上运行多个操作系统成为可能。下图展示了hypervisor mode。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l9bdZlMa-1642060160914)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/10_interrupt_image003.png)]

​ 如果实施了虚拟化扩展,则存在与以前的体系结构不同的特权模型。在非安全状态下,可以有三个特权级别,分别为PL0、PL1和PL2。

  • PL0: PL0是在用户模式下执行的应用程序软件的特权级别。在用户模式下执行的软件称为非特权软件。该软件无法访问该体系结构的某些功能。特别是,它不能更改许多配置设置。在PL0执行的软件只能进行非特权内存访问。
  • PL1 :PL1是指除用户模式和Hyp模式以外的所有模式。通常,操作系统软件在PL1执行,应用程序将在PL0(用户模式)下执行。
  • PL2:hypervisor使用PL2 Hyp mode,该模式可以控制并切换在PL1执行的不同的guest OS。如果实施了虚拟化扩展,则hypervisor将在PL2(Hyp模式)下执行。hypervisor将控制并使多个操作系统能够在同一处理器系统上共存并执行。

​ 下图显示了不同处理器状态下处理器可以处于的模式以及特权级别:

​ 处理器模式和安全状态以及特权等级的关系如下表所示:

​ 这些特权级别与TrustZone安全和普通(非安全)设置是分开的。

​ 特权级别定义了在当前安全状态下访问资源的能力,并不暗含在其他安全状态下访问资源的能力。

​ 通用操作系统(例如Linux)及其应用程序应在非安全状态下运行。安全状态一般由供应商特定的固件或对安全性敏感的软件来运行。一般在安全状态下运行的软件比在非安全状态下运行的软件具有更高的特权。

​ 当前的处理器模式和执行状态包含在当前程序状态寄存器(CPSR)中。可以通过异常或者在特权软件下显式地修改来进入到不同处理器模式。

10.1.1 寄存器

​ ARM体系结构提供了十六个32位通用寄存器(R0-R15)供软件使用0。其中的15个(R0-R14)可用于通用数据存储,而R15是程序计数器,其值随处理器执行指令而改变。显式地写入R15可以更改程序流程。软件还可以访问CPSR和SPSR中保存的先前运行模式的CPSR的副本。

​ 同一个寄存器可能在不同模式下对应物理上不同的位置。只有特定模式下才能访问到这些位置的寄存器。

​ 有些寄存器,不同的工作模式下有自己的副本,当切换到另一个工作模式时,那个工作模式的寄存器副本将被使用,这些寄存器被称为备份寄存器。备份寄存器在物理上使用不同的存储,通常仅在特定模式下才可以访问它们。下图中带阴影标记的寄存器都是备份寄存器。

​ 在所有模式下,“低位寄存器”和R15共享相同的物理存储位置。图3-5显示了某些模式下的某些“高位寄存器”被备份。例如,FIQ模式下使用R8-R12备份寄存器,也就是说,FIQ模式下对其的访问将转到另一个物理存储位置。对于除用户和系统模式以外的所有模式,R13和SPSR都是备份寄存器。

​ 对于备份寄存器,软件通常不会指定要访问哪个模式下的寄存器,这是由当前运行的模式隐含的。例如,访问R13的程序在用户模式下将访问R13_usr,在SVC模式下将访问R13_svc。

​ R13(在所有模式下)是堆栈指针,但是当堆栈操作不需要时,它可以用作通用寄存器。

​ R14(链接寄存器)保存BL分支指令的下一条指令的地址。当它不支持子程序的返回时,它也可以用作通用寄存器。R14_svc,R14_irq,R14_fiq,R14_abt和R14_und同样用于在发生中断和异常时,或者执行转移和链接指令时,备份R15的返回值。

​ R15是程序计数器并保存当前程序地址(实际上,在ARM状态下,它始终指向当前指令之前的八个字节,而在Thumb状态下,它始终指向当前指令之前的四个字节,这是原始ARM1的三级流水线的遗留特性)。在ARM状态下读取R15时,位[1:0]为零,位[31:2]包含PC值。在Thumb状态下,位[0]始终读为零。

​ R0-R14的复位值是不定的。在使用堆栈之前,必须通过引导代码初始化SP(堆栈指针)(针对每种模式)。ARM体系结构过程调用标准(AAPCS)或ARM嵌入式ABI(AEABI)指定了软件应如何使用通用寄存器,以便在不同的工具链或编程语言之间进行互操作。

Hypervisor模式

​ 支持虚拟化扩展的实现在管理程序(Hyp)模式下具有可用的其他寄存器。 Hypervisor模式以PL2特权级别运行。它可以访问自己的R13(SP_hyp)和SPSR副本。它使用用户模式下的链接寄存器存储函数的返回地址,并具有专用寄存器ELR_hyp来存储异常返回地址。 “ Hyp”模式仅在“正常”世界中可用,并提供虚拟化功能。

10.1.2 状态寄存器

​ 程序状态寄存器(CPSR,current programmer status register)包含处理器的状态和一些控制标记位。

​ 条件标记,bits[31:28]

​ 根据指令的执行结果设置,这些标记位是

​ N,bit[31]负数标记位

​ Z,bit[30]零标记位

​ C,bit[29]进位标记位

​ V,bit[28]溢出标记位

​ 这些条件标记位可以在任何模式下读写

​ GE[3:0],bit[19:16]一些SIMD指令使用

​ IT[7:2],bit[15:10]Thumb2指令集的If-then条件指令使用

​ J, bit[24] 处理器是否处于Jazelle状态,和T, bit[5]一起决定执行的指令集

​ E, bit[9] 大小端状态位,0表示小端,1表示大端。

​ Mask bits, bits[8:6]

​ A, bit[8] 异步中止禁止位Asynchronous abort mask bit.

​ I, bit[7] IRQ 禁止位

​ F, bit[6] FIQ 禁止位

​ Q,bit[27]为1的话,表明执行一些指令时出现饱和或者溢出,一般与DSP有关。

​ T, bit[5] Thumb指令位,和J,bit[24]位决定了处理器的指令集,ARM,Thumb,Jazzelle或者ThumbEE

​ M[4:0], bits[4:0]工作模式位,决定了处理器当前处于的工作模式。

​ 处理器可以使用直接写入CPSR模式位来实现模式之间切换。更常见的是,处理器会由于异常事件而自动切换模式。在用户模式下,无法改变处理器模式的PSR位[4:0]来切换模式和A,I和F位来使能或者禁止异步中止、IRQ和FIQ。

10.1.3 协处理器CP15

​ CP15是系统控制协处理器,可控制处理器核的许多功能。它可以包含16个32位主寄存器。对CP15的访问受权限控制,并且在用户模式下并非所有寄存器都可用。CP15寄存器访问指令指定所需的主寄存器,指令中的其他字段用于更精确地定义访问并增加CP15中的物理32位寄存器的数量。CP15中的16个主要寄存器的名称为c0至c15,但通常使用名称来引用。例如,CP15系统控制寄存器称为CP15.SCTLR。

​ 通过从一个通用寄存器(Rt)读取或写入位于CP15内的一组寄存器(CRn)中,可以控制系统架构的某些功能。该指令的Op1,Op2和CRm字段也可以用于选择寄存器或操作。 格式如下所示:

​ 从CP15寄存器读值到ARM寄存器

​ MRC p15, Op1, Rt, CRn, CRm, Op2 ; read a CP15 register into an ARM register

​ 从ARM寄存器写值到CP15寄存器

​ MCR p15, Op1, Rt, CRn, CRm, Op2 ; write a CP15 register from an ARM register

10.1.3.1 System control register (SCTLR)

​ SCTLR是通过CP15访问的寄存器,它控制存储器,系统功能,并提供反映处理器核中实现的功能的状态信息。

​ 系统控制寄存器只能从PL1或更高特权等级访问。

​ 各个位的描述如下:

​ •TE –Thumb异常使能。这可以控制异常进入ARM状态,还是Thumb状态。

​ •NMFI –是否支持不可屏蔽的FIQ(NMFI)支持。

​ •EE =异常字节序。这定义了在异常时的字节序,的CPSR.E位的值。

​ •U –使用对齐模型。

​ •FI – FIQ配置启用。

​ •V –该位选择异常向量表的基地址。

​ •I –指令缓存使能位。

​ •Z –分支预测使能位。

​ •C –缓存使能位。

​ •A –对齐检查使能位。

​ •M –启用MMU。

​ 引导代码序列的一部分通常将是设置CP15:SCTLR系统控制寄存器中的Z位,以启用分支预测功能。操作的代码如下:

MRC p15, 0, r0, c1, c0, 0 ; Read System Control Register configuration data

ORR r0, r0, #(1 << 2) ; Set C bit

ORR r0, r0, #(1 << 12) ; Set I bit

ORR r0, r0, #(1 << 11) ; Set Z bit

MCR p15, 0, r0, c1, c0, 0 ; Write System Control Register configuration data

10.2 异常处理

​ 异常(exception)是指处理器核中止正常的执行流程,并根据异常类型转去执行特定的软件例程(称为异常处理程序)。异常是通常需要采取补救措施或由特权软件更新系统状态以确保系统平稳运行的条件或系统事件。这称为处理异常。处理完异常后,特权软件将为处理器做好准备,以恢复发生异常之前的所有操作。其他体系结构可能会将ARM所谓的异常称为陷阱(traps)或中断(interrupts),但是,在ARM体系结构中,这些术语保留用于特定类型的异常。

​ 所有微处理器都必须响应外部异步事件,例如按下按钮或时钟达到某个值。通常,有专门的硬件可以激活处理器的输入线。这导致处理器核暂时停止当前的程序并执行特殊的特权处理程序例程。处理器核可以响应此类事件的速度可能是系统设计中的关键问题,称为中断等待时间(interrupt latency)。确实,在许多嵌入式系统中,并没有这样的主程序,系统的所有功能都由从中断代码来驱动,为这些中断分配优先级是设计的关键领域。系统不是通过不断地测试不同的标志位以查看是否有要做的事情,而是通过生成中断来通知处理器核,有事情必须要处理。复杂的系统有许多中断源,它们具有不同的优先级,并且支持中断嵌套,其中较高优先级的中断可以中断较低优先级的中断。

​ 在正常程序执行中,程序计数器在地址空间中递增,程序中的分支指令会修改执行流程,例如,函数调用,循环和条件代码。当发生异常时,此预定的执行顺序将中断,并暂时切换到异常处理程序以处理该异常。

​ 除了响应外部中断外,还有许多其他因素可能导致处理器核发生异常,包括外部(例如,复位),来自内存系统的异常终止以及内部(例如MMU生成的异常终止或通过SVC指令进行的OS调用)异常。处理异常会导致CPU核在模式之间切换并将某些寄存器复制到其他寄存器中。

10.2.1 异常的类型

​ ARMv7-A和ARMv7-R体系结构支持多种处理器模式,称为FIQ,IRQ,Supervisor,中止,未定义和系统的六种特权模式以及非特权的用户模式。如果实施了虚拟化扩展和安全扩展,则可以将Hyp模式和Monitor模式添加到列表中。当前模式可以在特权模式下改变,或者在发生异常时自动改变。

​ 非特权用户模式不能直接影响处理器核的异常行为,但是可以使用SVC异常以请求特权服务。这是用户应用程序请求操作系统来完成任务的方式。

​ 发生异常时,内核将保存当前状态和返回地址,进入特定模式,并可能禁用硬件中断。特定的异常程序处理从一个称为该异常的异常向量的固定内存地址开始执行。特权软件可以将异常向量表的起始位置编程到系统寄存器中,并且在获取相应的异常时会自动执行它们。

​ 存在以下异常类型:

​ (1)中断

​ ARMv7-A内核提供两种中断,称为IRQ和FIQ。

​ FIQ的优先级高于IRQ。由于FIQ在向量表中的位置以及FIQ模式下可用的更多的备份寄存器,因此FIQ还具有一些潜在的速度优势。这样可以节省将寄存器保存到堆栈时耗费的时钟周期。这两种异常通常都与处理器核上的输入引脚相关联-外部硬件会触发一条中断请求线,并在当前指令完成执行时引发相应的异常处理(假定不禁用该中断)。

​ FIQ和IRQ都是发给处理器核的物理信号,并且在触发时处理器核的FIQ和IRQ处于打开状态,它将处理相应的异常。几乎在所有系统上,通过使用中断控制器连接各种中断源。中断控制器对中断进行仲裁并确定优先级,然后依次提供串行化的单个信号,然后将其连接到内处理器核核的FIQ或IRQ引脚。

​ 由于IRQ和FIQ中断的发生与在任何给定时间内核所执行的软件都不直接相关,因此将它们分类为异步异常。

​ (2)中止

​ 中止可以在指令预取失败(预取中止)或数据访问失败(数据中止)时生成。它们可以来自外部存储器系统,在存储器访问时给出错误响应(可能表明指定的地址不对应于系统中的实际存储器)。另外,中止可以由内核的内存管理单元(MMU)生成。操作系统可以使用MMU中止来为应用程序动态分配内存。

​ 预取一条指令时,可以在指令流水线中中将其标记为已中止。仅当内核尝试执行它时,才导致预取中止异常。异常发生在指令执行之前。如果标记为中止的指令到达指令流水线的执行阶段之前刷新了指令流水线,则不会发生中止异常。数据中止异常发生在加载或存储指令执行时,并且是在尝试读取或写入数据之后发生的。

​ 如果中止是由于指令流的执行或尝试执行而产生的,则中止被描述为同步的,并且返回地址将提供导致该中止的指令的详细信息。

​ 异步的中止不是由执行指令生成,异步中止的返回地址可能不提供导致中止的原因的信息。

​ ARMv7体系结构分为精确的和不精确的异步中止。MMU产生的中止总是同步的。ARMv7体系结构不需要外部中止的类型是同步的。例如,在一个特定的实现上,页表翻译时报告的外部异常中止被认为是精确的,但这并不是所有处理器核都需要的。对于精确的异步中止,中止处理程序可以确定是哪条指令导致了中止,并且在该指令之后没有执行其他指令。这与不精确的异步异常中止相反,异步异常中止是外部存储器系统报告有关无法识别的访问的错误时的结果。在这种情况下,中止处理程序无法确定是哪条指令导致了问题,或者在产生中止的指令之后是否还会执行其他指令。

​ 例如,如果缓冲写入从外部存储系统接收到错误响应,则执行存储指令后很可能执行了其他指令。这意味着中止处理程序无法修复此问题并返回到应用程序。它所能做的就是杀死导致问题的应用程序。因此,设备探测需要特殊的处理,因为从外部报告的对不存在区域的读取中止将产生不精确的同步中止,即使将此类存储器标记为“strong odered”或“设备”。

​ 异步中止的检测由CPSR A位控制。如果将A位置1,CPU核将识别出外部存储系统的异步异常中止,但不会产生中止异常。取而代之的是,内核将中止挂起状态挂起,直到清除A位时才采取异常处理为止。内核代码将使用屏障指令来确保针对正确的应用程序识别未处理的异步中止。如果由于不精确的中止而不得不终止线程,则该线程必须是正确的线程。

​ (3)复位

​ 所有处理器核都有复位输入,并且在复位后将立即执行复位异常。它是最高优先级的异常,无法屏蔽。上电后,此异常用于在处理器核上执行代码以对其进行初始化。

​ (4)生成异常的指令

​ 某些指令的执行会产生异常。通常执行以下指令,以便从更高特权级别的软件中请求服务:

​ •Supervisor Call(SVC)指令使用户模式程序可以请求操作系统服务。

​ •如果实施了虚拟化扩展,则可以使用Hypervisor调用(HVC)指令,使虚拟机可以请求Hypervisor服务。

​ •如果实施了安全扩展,则可以使用(SMC)指令,使普通环境可以请求安全环境服务。

​ 任何试图执行处理器核无法识别的指令都会产生未定义的异常。

​ 发生异常时,CPU核将执行与该异常对应的处理程序。异常处理程序在内存中的存储位置称为异常向量。在ARM体系结构中,异常向量存储在称为异常向量表的表中。因此,用于特定异常的向量可以位于异常向量表起始位置的固定偏移处。该向量表的基地址由特权软件在系统寄存器中指定,以便处理器核可以在发生异常时找到相应的处理程序。

​ 可以为安全PL1,非安全PL1,安全监视器和非安全PL2特权级别分别配置单独的异常向量表。处理器核根据当前的特权等级和安全状态来查找对应的异常向量表。

​ 可以用ARM或Thumb代码编写异常处理程序。CP15 SCTLR.TE位用于指定异常处理程序将使用ARM还是Thumb指令集。处理异常时,必须保留处理器核先前的模式,状态和寄存器,以便可以在处理异常后恢复原来程序的执行。

10.2.2 异常优先级

​ 当异常同时发生时,将依次处理每个异常,然后返回原来执行的应用程序。所有异常不可能同时发生。例如,未定义指令(Undef)和supervisor call(SVC)异常是互斥的,因为它们都是由执行指令触发的。

​ 注意:ARM体系结构未定义何时采用异步异常。因此,异步异常相对于其他异常(同步和异步)的优先级由实现决定。

​ 所有异常均禁用IRQ,只有FIQ和复位禁用FIQ。这是由处理器核自动设置CPSR I(IRQ)和F(FIQ)位来完成的。

​ 可能同时产生多个异常,但是某些组合是互斥的。预取中止将一条指令标记为无效,因此不能与未定义的指令或SVC同时发生(当然,SVC指令也不能是未定义的指令)。这些指令不会导致任何内存访问,因此不会导致数据中止。该体系结构未定义何时必须采取异步异常,FIQ,IRQ或异步异常中止,但是采用IRQ或数据异常中止不会禁用FIQ异常这一事实意味着FIQ执行将优先于IRQ或异常中止异常。

​ 异常处理是通过使用称为向量表的内存区域来控制的。默认情况下,该地址位于字映射地址从0x00到0x1C的内存映射的底部。向量表可以从0x0移到0xFFFF0000。

​ 对于带有Security Extensions的内核,情况更加复杂。这里有三个向量表,非安全向量表,安全向量表和安全监视器向量表。对于带有Virtualization Extension的核心,有四个,添加了Hypervisor向量表。对于具有MMU的内核,所有这些向量地址都是虚拟的。下表总结了各种状态下异常的行为。

​ 进入异常时,CPSR的I和F设置如下表所示:

10.2.3 向量表

​ 向量表示触发异常时ARM核跳转到的指令表。这些指令位于内存中的特定位置。默认向量基址为0x00000000,但大多数ARM核允许将向量基址移至0xFFFF0000(或HIVECS)。所有Cortex-A系列处理器都允许这样做,这是Linux内核选择的默认地址。实现安全扩展的内核还可以使用CP15向量基地址寄存器为安全状态和非安全状态分别设置向量基地址。

​ 每种异常类型都有一个字的地址。因此,每个异常只能在向量表中放置一条指令(尽管从理论上讲,可以使用两条16位Thumb指令)。因此,向量表条目几乎总是包含以下两种形式的分支之一。

​ B

​ 这将执行PC相对跳转。它可以跳转到当前指令的前后32MB。

​ LDR PC,[PC,#offset]

​ 这将从地址相对于异常指令offset偏移量的值加载到PC。这样就可以将异常处理程序放置在32位内存地址空间内的任意地址处(但相对于B指令,要多花一些额外的指令周期)。

​ 当处理器核以Hyp mode运行时,它使用Hyp mode向量入口地址,这些入口地址是从Hyp mode的专用向量表中获取的。通过Hyp trap entry的特定异常过程进入系统管理程序模式,该过程利用向量表中先前保留的0x14地址。专用寄存器(Hyp Syndrome Register)向hypervisor提供有关进入管理程序的异常或其他原因的信息(例如,陷阱CP15操作)。

​ 异常向量和异常基地址

​ 发生异常时,处理器根据强制到跟异常的类型相应的地址去执行。这个地址被称为异常向量。一组异常向量包括从一个异常向量基地址开始的连续的8个字对齐的地址空间。8个异常向量组成异常向量表。

​ 可能的异常向量基地址或者异常向量表的量由扩展类型和架构类型决定,这里只讨论包含安全扩展的类型

​ 包含安全扩展类型的实现有如下向量表

​ 一个转去secure monitor模式的表,MVBAR保存了异常向量基地址

​ 一个转去secure PL1模式(不包括monitor模式)的表,secure状态下SCTLR.V位决定了向量表的基地址,如果V0的话,secure VBAR保存了异常向量基地址;如果v1的话,异常向量基地址为0xFFFF0000。

​ 一个转去非安全PL1模式的表,这是非安全状态的向量表,Non-secure状态下SCTLR.V位决定了向量表的基地址,如果V0的话,Non-secure VBAR保存了异常向量基地址;如果v1的话,异常向量基地址为0xFFFF0000。

10.2.4 FIQ and IRQ

​ FIQ保留用于需要保证快速响应时间的单个高优先级中断源,而IRQ用于系统中的所有其他中断。

​ 由于FIQ是向量表中的最后一项,因此FIQ处理程序可以直接放置在向量入口位置,并从该地址开始顺序运行。这避免了分支指令和任何相关的延迟,从而加快了FIQ响应时间。相对于其他模式,FIQ模式下可用的备份寄存器数量比较多,从而避免要将寄存器的值保存到栈上,提高了执行速度。

​ Linux通常不使用FIQ。由于内核与体系结构无关,因此它不具有多种形式的中断的概念。某些运行Linux的系统仍可以使用FIQ,但是由于Linux内核从不禁用FIQ,因此它们比系统中的其他任何事物都具有优先权,因此需要格外小心。

10.2.5 返回指令

​ 处理异常后,链接寄存器(LR)用于为存储返回地址。下表提供了包含此调整的异常的返回指令。

10.2.6 异常处理

​ 发生异常时,ARM内核会自动执行以下操作:

​ 1.将CPSR复制到SPSR_ ,这是特定(非用户)操作模式的备份寄存器。

​ 2.将返回地址存储在新模式的链接寄存器(LR)中。

​ 3.将CPSR模式位修改为与异常类型相关联的模式。

​ •其他CPSR模式位设置由CP15系统控制寄存器的值确定。

​ •T位设置为CP15 TE位给定的值。

​ •J位被清除,E位(字节序)被设置为EE(异常字节序)位的值。

​ 这使异常始终以ARM或Thumb状态运行,并且以小端或大端运行,无论CPU核在异常之前处于何种状态。

​ 4.将PC设置为指向异常向量表中的相关指令。

​ 在新模式下,CPU核将访问与该模式关联的寄存器。

​ 异常处理程序软件几乎总是需要在进入异常处理程序时立即将寄存器保存到堆栈中。FIQ模式具有更多的备份寄存器,因此可以编写不使用堆栈的简单处理程序。

​ 提供了一种特殊的汇编语言指令来帮助保存必要的寄存器,称为SRS(store return state存储返回状态)。该指令将LR和SPSR压入任何模式的堆栈;所使用的堆栈由指令操作数指定。

10.2.6.1 从异常处理程序返回

​ 要从异常处理程序返回,必须进行两个单独的操作:

​ 1.从保存的SPSR中恢复CPSR。

​ 2.将返回地址偏移量设置到PC。

​ 在ARM体系结构中,这可以通过使用RFE指令或以PC作为目标寄存器的任何标志设置数据处理操作(带有S后缀)来实现,例如SUBS PC,LR,#offset(注意S)。异常返回(RFE)指令将链接寄存器和SPSR从当前模式堆栈弹出。

​ 有多种方法可以实现此目的。

​ •可以使用数据处理指令来调整LR并将其复制到PC中,例如:

SUBS PC, LR,4

​ 指定S表示同时将SPSR复制到CPSR。

​ 如果异常处理程序入口代码使用堆栈来存储在处理异常时必须保留的寄存器,则它可以使用带有^限定符的加载指令返回。例如,异常处理程序可以使用以下命令在一条指令中返回:

LDMFD sp!, pc ^

LDMFD sp!, R0-R12,pc ^

​ 在此示例中,^限定符表示SPSR同时复制到CPSR。

​ 为此,异常处理程序必须将以下内容保存到堆栈中:

​ —调用处理程序时,所有需要使用的工作寄存器。

​ —修改链接寄存器以产生与数据处理指令相同的效果。

​ 注意

​ 不能使用16位Thumb指令从异常中返回,因为这些指令无法还原CPSR。RFE指令从当前模式的堆栈中恢复PC和SPSR。

RFEFD sp!

10.2.7 中止处理程序

​ 中止处理程序代码在系统之间可能有很大差异。在许多嵌入式系统中,异常中止表示意外错误,处理程序将记录所有诊断信息,报告错误并让应用程序(或系统)退出。

​ 在使用MMU支持虚拟内存的系统中,中止处理程序可以将所需的虚拟页加载到物理内存中。实际上,它尝试解决最初中止的原因,然后返回中止的指令并重新执行它。

​ CP15寄存器提供了导致中止的存储器访问地址(故障地址寄存器Fault Address Register)和中止的原因(故障状态寄存器Fault Status Register)。原因可能是缺少访问权限,外部中止或地址转换错误。此外,链接寄存器(进行了–8或–4调整,取决于中止是由指令获取还是数据访问引起的),给出了导致中止异常的指令的地址。通过检查这些寄存器,最后执行的指令以及系统中可能的其他内容(例如转换表条目),中止处理程序可以确定要采取的操作。

10.2.8 未定义的指令处理

​ 如果CPU核尝试使用操作码执行一条指令(在ARM体系结构规范中描述为UNDEFINED),或者执行了协处理器指令但没有协处理器将其识别为可以执行的指令,则会导致未定义的指令异常。

​ 在某些系统中,代码可能包含用于协处理器(例如VFP协处理器)的指令,但是系统中不存在相应的VFP硬件。另外,VFP硬件有可能无法处理特定指令,而是想调用软件来对其进行仿真。或者,可能会禁用VFP硬件,采用异常处理,以便可以启用它,然后重新执行指令。

​ 通过未定义的指令向量调用此类仿真器。他们检查导致异常的指令操作码,并确定要采取的措施(例如,在软件中执行适当的浮点运算)。在某些情况下,可能必须将这些处理程序以菊花链方式链接在一起(例如,可能要模拟多个协处理器)。

​ 如果没有软件使用未定义的指令或协处理器指令,则异常的处理程序必须记录适当的调试信息,并杀死由于应用程序。

​ 在某些情况下,未定义指令异常的另一个用途是实现用户断点。

10.2.9 SVC异常处理

​ supervisor call(SVC)通常用于使用户模式代码能够访问OS功能。例如,如果用户代码想要访问系统的特权部分(例如执行文件I / O),则通常将使用SVC指令执行此操作。

​ 可以使用寄存器或者操作码中某个字段将参数传递给SVC处理程序。

​ 发生异常时,异常处理程序可能必须确定内核是处于ARM还是Thumb状态。

​ 特别是SVC处理程序,可能必须读取指令集状态。这是通过检查SPSR T位完成的。该位设置为Thumb状态,清除为ARM状态。

​ ARM和Thumb指令集都具有SVC指令。从Thumb状态调用SVC时,必须考虑以下因素:

​ •指令地址位于LR-2,而不是LR-4。

​ •指令本身是16位的,因此需要半字加载,

​ • SVC编号为8位而不是ARM状态下的24位。

​ 例11-1中显示了说明Linux内核使用SVC的代码

​ SVC#0指令使ARM核采用SVC异常(一种访问内核功能的机制)。寄存器R0定义所需的系统调用(在本例中为sys_write)。其他参数在寄存器中传递。对于sys_write,您需要R0告诉要写入的位置,R1指向要写入的字符,R2给出字符串的长度。

10.3 und异常模示程序示例

10.3.1 代码分析

​ 此节代码所在**裸机Git仓库 NoosProgramProject/(10_异常与中断/008_exception_undef)**目录下。

​ 通过在代码段里里插入一个未定义指令(0xdeadc0de),从而产生未定义指令异常。在未定义异常指令异常的处理函数里,调用printException函数,打印出当前的CPSR值,和产生异常的原因的字符串。

​ 在复位Reset_Handler里要分别设置好SVC模式和und模式的栈,这样我们就可以在各自的模式里调用C代码。通过如下指令,设置好异常向量的基地址

mcr p15, 0, r0, c12, c0, 0

​ 在异常向量表里,通过如下指令跳转到Undefined_Handler标签处

ldr pc, =Undefined_Handler

​ 在Undefined_Handler里将r0-r12和lr保存在und模式的栈上,然后调用printException打印当前的CPSR值,和产生异常的原因的字符串。最后将r0-r12从栈上恢复,lr从栈上弹出到PC,并同时将SPSR恢复到CPSR,从而返回去执行出现未定义异常指令的下一条指令。

​ 代码如下008_exception_undef\\start.S:

.text
.global  _start, _vector_table
_start:
_vector_table:
	ldr 	pc, =Reset_Handler			 /* Reset				   */
	ldr 	pc, =Undefined_Handler		 /* Undefined instructions */
	//b Reset_Handler
	//b Undefined_Handler
	b halt//b SVC_Handler//ldr 	pc, =SVC_Handler			 /* Supervisor Call 	   */
	b halt//ldr 	pc, =PrefAbort_Handler		 /* Prefetch abort		   */
	b halt//ldr 	pc, =DataAbort_Handler		 /* Data abort			   */
	.word	0							 /* RESERVED			   */
	b halt//ldr 	pc, =IRQ_Handler			 /* IRQ interrupt		   */
	b halt//ldr 	pc, =FIQ_Handler			 /* FIQ interrupt		   */

.align 2
Undefined_Handler:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, r0-r12, lr  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, r0-r12, pc^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"

.align 2
Reset_Handler:
	/* Reset SCTlr Settings */
	mrc 	p15, 0, r0, c1, c0, 0	  /* read SCTRL, Read CP15 System Control register		*/
	bic 	r0,  r0, #(0x1 << 13)	  /* Clear V bit 13 to use normal exception vectors  	*/
	bic 	r0,  r0, #(0x1 << 12)	  /* Clear I bit 12 to disable I Cache					*/
	bic 	r0,  r0, #(0x1 <<  2)	  /* Clear C bit  2 to disable D Cache					*/
	bic 	r0,  r0, #(0x1 << 2)	  /* Clear A bit  1 to disable strict alignment 		*/
	bic 	r0,  r0, #(0x1 << 11)	  /* Clear Z bit 11 to disable branch prediction		*/
	bic 	r0,  r0, #0x1			  /* Clear M bit  0 to disable MMU						*/
	mcr 	p15, 0, r0, c1, c0, 0	  /* write SCTRL, Write to CP15 System Control register	*/

    cps     #0x1B                /* Enter undef mode                */
    ldr     sp, =0x80300000     /* Set up undef mode stack      */

    cps     #0x13                /* Enter Supervisor mode         */
    ldr     sp, =0x80200000     /* Set up Supervisor Mode stack  */
	ldr r0, =_vector_table
	mcr p15, 0, r0, c12, c0, 0  /* set VBAR, Vector Base Address Register*/
	//mrc p15, 0, r0, c12, c0, 0  //read VBAR

	bl clean_bss
	
	bl system_init

und_code:
	.word 0xdeadc0de  /* undefine instruction */
	//.word 0xFFFFFFFF

	bl main

halt:
	b  halt

clean_bss:
	/* 清除BSS段 */
	ldr r1, =__bss_start
	ldr r2, =__bss_end
	mov r3, #0
clean:
	cmp r1, r2
	strlt r3, [r1]
	add r1, r1, #4
	blt clean
	
	mov pc, lr

10.3.2 参考章节《4-1.4编译程序》编译程序

10.3.3 参考章节《4-1.4映像文件烧写、运行》烧写、运行程序

​ 此时观察串口打印

10.4 swi异常模示程序示例

10.4.1 代码分析

​ 此节配套源码所在裸机Git仓库 NoosProgramProject/(10_异常与中断/008_exception_swi) 目录下。

​ 然后通过swi 123,执行管理模式异常,程序跳转到异常向量表偏移0x8的地方执行,在异常向量表里通过如下指令跳转到SVC_Handler标签处执行。

ldr pc, =SVC_Handler

​ 在SVC_Handler里将r0-r12和lr保存在SVC模式的栈上,然后将lr的值移动到R4,调用printException函数打印出当前的CPSR值,和产生异常的原因的字符串。将R4减去4,赋值给R0,也就是swi指令所在的地址,然后调用printSWIVal函数打印出swi指令的参数。最后将r0-r12从栈上恢复,lr从栈上弹出到PC,并同时将SPSR恢复到CPSR,从而返回去执行swi指令的下一条指令。代码如下(008_exception_swi\\start.S):

.text
.global  _start, _vector_table
_start:
_vector_table:
   ldr 	pc, =Reset_Handler			 /* Reset				   */
   ldr 	pc, =Undefined_Handler		 /* Undefined instructions */
   ldr 	pc, =SVC_Handler			 /* Supervisor Call 	   */
   b halt//ldr 	pc, =PrefAbort_Handler		 /* Prefetch abort		   */
   b halt//ldr 	pc, =DataAbort_Handler		 /* Data abort			   */
   .word	0							 /* RESERVED			   */
   b halt//ldr 	pc, =IRQ_Handler			 /* IRQ interrupt		   */
   b halt//ldr 	pc, =FIQ_Handler			 /* FIQ interrupt		   */

.align 2
Undefined_Handler:
   /* 执行到这里之前:
    * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_und保存有被中断模式的CPSR
    * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
    * 4. 跳到0x4的地方执行程序 
    */

   /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
   /* lr是异常处理完后的返回地址, 也要保存 */
   stmdb sp!, r0-r12, lr 

   /* 保存现场 */
   /* 处理und异常 */
   mrs r0, cpsr
   ldr r1, =und_string
   bl printException
   
   /* 恢复现场 */
   ldmia sp!, r0-r12, pc^  /* ^会把spsr的值恢复到cpsr里 */
   
und_string:
   .string "undefined instruction exception"

.align 2
SVC_Handler:
   /* 执行到这里之前:
    * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_svc保存有被中断模式的CPSR
    * 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
    * 4. 跳到0x08的地方执行程序 
    */

   /* 保存现场 */
   /* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
   /* lr是异常处理完后的返回地址, 也要保存 */
   stmdb sp!, r0-r12, lr  

   mov r4, lr
   
   /* 处理swi异常 */
   mrs r0, cpsr
   ldr r1, =swi_string
   bl printException

   sub r0, r4, #4
   bl printSWIVal
   
   /* 恢复现场 */
   ldmia sp!, r0-r12, pc^  /* ^会把spsr的值恢复到cpsr里 */
   
swi_string:
   .string "swi exception"

.align 2
Reset_Handler:
   /* Reset SCTlr Settings */
   mrc 	p15, 0, r0, c1, c0, 0	  /* read SCTRL, Read CP15 System Control register		*/
   bic 	r0,  r0, #(0x1 << 13)	  /* Clear V bit 13 to use normal exception vectors  	*/
   bic 	r0,  r0, #(0x1 << 12)	  /* Clear I bit 12 to disable I Cache					*/
   bic 	r0,  r0, #(0x1 <<  2)	  /* Clear C bit  2 to disable D Cache					*/
   bic 	r0,  r0, #(0x1 << 2)	  /* Clear A bit  1 to disable strict alignment 		*/
   bic 	r0,  r0, #(0x1 << 11)	  /* Clear Z bit 11 to disable branch prediction		*/
   bic 	r0,  r0, #0x1			  /* Clear M bit  0 to disable MMU						*/
   mcr 	p15, 0, r0, c1, c0, 0	  /* write SCTRL, Write to CP15 System Control register	*/

   cps     #0x1B                /* Enter undef mode                */
   ldr     sp, =0x80300000     /* Set up undef mode stack      */

   cps     #0x13                /* Enter Supervisor mode         */
   ldr     sp, =0x80200000     /* Set up Supervisor Mode stack  */

   ldr r0, =_vector_table
   mcr p15, 0, r0, c12, c0, 0  /* set VBAR, Vector Base Address Register*/
   //mrc p15, 0, r0, c12, c0, 0  //read VBAR

   bl clean_bss
   
   bl system_init

und_code:
   .word 0xdeadc0de  /* undefine instruction */
   //.word 0xFFFFFFFF

swi_code:
   swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

   bl main

halt:
   b  halt
       
clean_bss:
   /* 清除BSS段 */
   ldr r1, =__bss_start
   ldr r2, =__bss_end
   mov r3, #0
clean:
   cmp r1, r2
   strlt r3, [r1]
   add r1, r1, #4
   blt clean
   
   mov pc, lr

10.4.2 参考章节《4-1.4编译程序》编译程序

10.4.3 参考章节《4-1.4映像文件烧写、运行》烧写、运行程序

​ 此时观察串口打印

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OE1psG2q-1642060160922)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/10_interrupt_image014.png)]

10.5 中断处理

​ 较旧的ARM体系结构版本使实现者在设计外部中断控制器时具有很大的自由度,而无需就中断的数量或类型或用于与中断控制器模块接口的软件模型达成协议。通用中断控制器v2(GIC)架构提供了更为严格的规范,不同厂商的中断控制器之间具有更高的一致性。这使中断处理程序代码更易于移植。

10.5.1 外部中断请求

​ ARM核如何具有两个外部中断请求FIQ和IRQ。这两个都是对电平触发,对低电平有效。各个不同的实现都有中断控制器,这些控制器接受来自各种外部源的中断请求并将它们映射为FIQ或IRQ,从而导致ARM核发生异常。

​ 通常,只有当相应的CPSR禁止位(分别为F和I位)清零并且相应的输入为有效时,才可以产生中断异常。

​ CPS指令提供了一种简单的机制来启用或禁用由CPSR A,I和F位(分别为异步中止,IRQ和FIQ)控制的异常。

​ CPS IE或CPS ID将分别启用或禁用异常。使用字母A,I和F中的一个或多个指定要启用或禁用的异常。省略了相应字母的异常将不会被修改。

​ 在Cortex-A系列处理器中,可以配置CPU核,以使FIQ不能被软件屏蔽。这被称为不可屏蔽FIQ,并由CPU核复位时采样的硬件配置的输入信号控制。发生FIQ异常后,它们仍将自动被屏蔽。

10.5.2 分配中断

​ 中断控制器接受和仲裁来自各种源的中断。控制器通常包含多个寄存器,这些寄存器使运行的软件能够屏蔽各个中断源,确认来自外部设备的中断,为各个中断源分配优先级并确定当前需要处理的中断源。

​ 此中断控制器可以是特定于系统的设计,也可以是ARM通用中断控制器(GIC)架构的实现。

10.5.3 简单的中断处

​ 这代表了最简单的中断处理程序。发生中断时,将禁用其他同类中断,直到稍后显式启用。我们只能在第一个中断请求完成时才能处理其他中断,并且在此期间没有更高优先级或更紧急的中断需要处理。这通常不适用于复杂的嵌入式系统,但是解释更复杂的的示例之前了解是很有用的,在这种情况下是不可重入的中断处理程序。

​ 处理中断所采取的步骤如下:

​ 1.外部硬件引发IRQ异常。ARM核自动执行几个步骤。当前模式下PC的内容存储在LR_IRQ中。CPSR寄存器被复

以上是关于10_异常与中断的主要内容,如果未能解决你的问题,请参考以下文章

异常与中断2--und异常,swi异常,按键中断,定时器中断

ARM中的异常中断是如何实现进入中断程序的,比如如何进入...

python_异常处理

s5pv210——中断

stm32中断初识与实践(上)

异常中断向量表的地址问题