首页 > 代码库 > 《Cortex?-A系列编程者指南(V3.0)》第12章<异常处理>笔记
《Cortex?-A系列编程者指南(V3.0)》第12章<异常处理>笔记
在本章,我们看看ARM处理器如何响应异常。异常是任何需要挂起正常执行转而运行与每个异常类型相关联软件(称为异常处理程序)的条件。
12.1 异常的类型
如我们在第四章看到,A系列和R系列架构支持七种处理器模式,六种特权模式(称为快速中断模式、外部中断模式、管理模式、中止模式、未定义模式和系统模式),一种非特权模式(用户模式)。如果虚拟化扩展和安全扩展被实现,Hyp和Monitor模式可以被添加到列表。当前模式在软件控制下或处理一个异常时修改。
然而,非特权的用户模式只能通过产生一个异常来切换到另一个模式。
当一个异常发生,处理器保存当前状态和返回地址,进入一个指定的模式,并且可能会禁止硬件中断。执行从一个称为异常向量的固定的内存地址恢复。这自动地发生,不在编程者的直接控制之下。
有下列异常类型存在:
<中断>
ARMv7-A处理器上有两种类型的中断,称为IRQ和FIQ。FIQ比IRQ优先级高。FIQ有些潜在地速度优势,归因于它在向量表中的位置和在FIQ模式中可获取到更高数目的分组寄存器。这潜在地节省处理器在处理句柄中压入寄存器到堆栈的时钟周期。这类异常都典型地与处理器上的输入引脚相关联 --- 假设中断没有被禁止,外部硬件发出一个中断请求,在当前指令完成执行时,相应的异常类型被引发。
<中止>
中止可以从指令获取失败(预取中止)或数据访问失败(数据中止)中产生。它们可以来自外部存储器系统在存储器访问时给予一个错误响应(表示可能指定的地址与系统中实际内存不对应)。或者,中止可能会由处理器的内存管理单元(MMU)产生。操作系统可以使用MMU中止来动态分配内存给应用。
一个指令当它被获取时,可以在流水线中被标记为中止。预取中止异常仅在处理器确实尝试去执行它时。异常在指令实际执行前产生。如果流水线在中止指令到达流水线执行阶段之前被清空,中止异常不会发生。数据中止异常在加载或存储指令执行时发生,被认为在数据读或写已经尝试之后发生。
如果中止作为指令流执行或尝试执行后的结果,它被描述为同步的,返回地址会提供引起异常指令的细节。异步中止不是由正在执行指令产生的,同时返回地址可能不总是提供引起中止的细节。
ARMv7架构区分精确与不精确的异步中止。由MMU产生的中止总是同步的。架构不需要外部中止访问的特定类型是同步的。
例如,在一个特定的处理器实现上,它可能是这样的,报道了一个页表的步行外部中止被视为精确的,但对于所有的处理器,这不是必须的。对于精确的异步中止,中止处理程序可以确定哪个指令引起了中止,没有后续指令在那个指令之后被执行。这与不精确的异步中止作对比,它是外部存储器系统在一个无法识别的访问上报告一个错误时的结果。
在这种情况下,中止处理函数不能确定那个指令引起了这个问题(引起中止产生的指令之后的指令可能已经执行)。例如,如果一个缓冲的写从外部存储器系统接收到一个错误响应,后续指令会在存储之后已被执行。这意味着对于中止处理函数,修复问题并返回给应用程序是不可能的。所有它能做的就是杀死引起这个问题的应用。因此设备检测需要特殊的处理,因为在读取不到的区域中,外部报告的中止会产生不精确的同步中止,甚至在这样的内存被标记为强排序的或设备。
异步中止的检测由CPSR的A位控制。如果A位置位,来自外部存储器系统的异步中止会被处理器识别,中止异常不会立即产生。反而,处理器保持中止挂起直到A位被清除并在那时获取一个异常。典型地,内核代码会确保(通过使用一个屏障指令)挂起的异步中止会确认正确的应用程序。如果一个线程由于一个不精确的中止已被杀死,它需要是正确的一个。
<复位>
所有的处理器都有一个复位输入,并且会在它们被复位后立即产生复位异常。这是最高优先级的异常,不能被屏蔽。
<异常指令>
在ARM处理器上,有两类指令会引起异常。第一种是超级用户调用(Supervisor Call ,SVC),以前被称为软件中断(Software Interrupt,SWI)。典型地,这被用于提供一种机制,用户模式程序可以传递控制到特权的操作系统中的内核代码,来执行操作系统级任务。第二种是未定义指令。架构定义了特定位模式与未定义操作码对应。尝试其中之一会引起一个未定义指令异常的产生。另外,对于没有相应协处理器硬件执行的协处理器指令,也会引起这一陷阱产生。一些指令只能在特权模式被执行,在用户模式执行这些指令会引起未定义指令异常。
当一个异常发生时,代码执行传递到一个被称为向量表的内存区域。在表内,每一种异常类型只分配了一个字,通常包含一个到实际异常处理函数的分支指令。这个行为与大多数其它的架构不同,其它的架构通常在异常表中存储一个执行地址,而不是一条指令。
你可以在ARM或Thumb代码中编写异常处理函数。CP15 SCTLR.TE位被用于指定异常处理函数是否会使用ARM或Thumb。当异常处理时,先前处理器的模式、状态和寄存器必须保藏,从而使程序在异常处理之后可以恢复。
12.2 异常模式总结
表12.1列出了中断的状态,在进入一个异常处理韩式时禁止了CPSR的I和F位。
12.2.1 异常优先级
因为一些异常类型会同时发生,处理器为每个异常赋予一个固定的优先级。未定义指令、预取中止和超级用户调用是由于一个指令(有为未定义模式和SVC模式位操作码的特定位模式)的执行,因而不会同时发生。因此它们具有相同的优先级。
注意:ARM架构并未定义何时异步异常发生。因此,异步异常的优先级相对于其它优先级,同步和异步,是实现定义的。
在异常的优先级和实际异常处理代码之间作区分是重要的,这发生在多个异常同时请求时。表12-1包含一列讲述FIQ和IRQ如何自动被一些异常禁止。(所有的异常禁止IRQ,只有FIQ和复位禁止了FIQ。)这由处理器自动设置CPSR I(IRQ)和F(FIQ)位完成。
因此,一个FIQ异常可以中断一个中止处理函数或IRQ异常。在数据中止和FIQ同时发生时,数据中止(具有更高优先级)首先进行。这允许处理器记录数据中止的返回地址。但是因为FIQ不能被数据中止禁止,我们然后立即接受FIQ的异常。在FIQ的最后,我们返回到数据中止处理函数。
超过一个异常可能潜在地同时发生,但是一些组合是相互排斥的。一个预取中止标记一条指令是无效的,因此不可能同时发生作为未定义指令或SVC(当然,SVC指令也不能是一条未定义指令)。这些指令不能引起任何内存访问,因此不会引起数据中止。架构未定义异步异常,FIQ,IRQ,或异步中止何时必须接受,但事实上,接受一个IRQ或数据中止异常并未禁止FIQ异常,意味着FIQ异常会相对IRQ或异步中止处理优先被考虑。
12.3 进入一个异常处理程序
当一个异常发生时,ARM处理器自动地做下列事情:
> 在新模式的链接寄存器(LR)中,保存下一条指令的地址。
> 拷贝CPSR到SPSR,SPSR是分组寄存器中的一个,特定于每种(非用户)操作模式。
> 修改CPSR模式位到与异常类型相关联的一个模式。其它的CPSR模式位的值设置由CP15系统控制寄存器决定。T位由CP15的TE位设置。J位被清除,E位(字节序)设置为EE(Exception Endianness,异常字节序)位的值。这使得异常总是运行在ARM或Thumb状态,小端或大端,相对于异常前的处理器状态。
> 强制PC指向异常向量表中相应的指令。
对于异常处理软件,在异常入口立即保存寄存器值到堆栈总是必要的。(FIQ模式具有跟多的专用寄存器,因此一个简单的处理函数可能能够以不需要使用堆栈的形式被编写。)
一条特殊的汇编语言指令提供协助,以节省必要的寄存器,被称为SRS(Store Return State)。这条指令使得LR和SPSR进入任何模式的堆栈;使用哪个堆栈由指令操作数指定。
12.4 从异常处理程序离开
为了从异常处理程序离开,两个独立地操作必须原子地进行:
> 从保存的SPSR恢复CPSR;
> 设置PC为返回地址偏移;
在ARM架构中,这可以通过使用RFE指令或任何以PC作为目的寄存器的标志设置数据处理操作(以S作为后缀)实现,例如,SUBS PC,LR,#offset(注意S)。异常返回(Return From Exception, RFE)指令从当前模式栈内pop链接寄存器和SPSR。
12.5 向量表
表12-1中的第1列给出了与特定类型异常相关联的向量表中的向量偏移。这是ARM处理器在异常抛出时跳转的指令表。这些指令位于内存中的特定位置。正常的向量基地址是0x00000000,但是绝大多数ARM处理器允许向量基地址被移动到0xFFFF0000(或HIVECS)。所有的Cortex-A系列处理器允许这点,并且这是Linux内核选择的默认地址。实现安全扩展的处理器可以额外地设置向量基地址,分别为安全和非安全状态,使用CP15向量基地址寄存器。
你会注意到只有一个单一字的地址与每种异常类型相关联。因此,每种异常只有一个单一的指令可以被放在向量表中(尽管,理论上,两个16位的Thumb指令可以被使用)。FIQ是不同的。因此,向量表入口几乎总是包含各种形式的分支之一。
B<label>
这执行了相对PC的分支。这对于调用在内存中足够近的异常处理代码是合适的,因为分支指令中提供的24位域足够大来编码这个偏移。
LDR PC, [PC, #offset]
这从一个相对异常指令地址定义的内存位置加载PC。这让异常处理程序被放置在全32位内存空间中的任意地址(相对前面简单的分支,花费一些额外的周期)。
12.6 返回指令
链接寄存器(LR)在异常处理之后被用于存储PC合适的返回地址。它的值需要如表12-1那样被修改,依赖于异常发生的类型。《ARM架构参考手册》(ARM Architecture Reference Manual)合适地定义了LR的值(这些定义来源的值,为的是早期硬件实现的方便)。