异常处理流程
当异常发生时,处理器有一套预定义好的处理序列来处理异常及从异常中恢复。如果发生异常,除了reset之外,其他异常都会执行完当前正在执行的那条指令,在进入异常处理流程。下列的处理流程都是自动进行的:
- CPSR被复制到SPSR_<mode>,模式就是异常要进入的模式(比如FIQ,IRQ等),各个具体模式见下图。
- 处理器切换到对应的异常模式,并设置CPSR中相关的位,比如模式位。同时在所有异常模式下IRQ中断都将被自动禁止,而FIQ中断只有进入FIQ中断/reset模式时才会被禁止。
- 切换到ARM状态(如果当前是thumb状态的话)
- 把PC值存储到 LR_<mode>,模式就是当前进入的异常模式
-
PC强制设置为对应异常的向量入口地址。
备注:上面所有这些操作都是异常发生时,ARM处理器硬件自动完成的,无需写任何一行代码。
上面自动处理完之后,处理器就会开始执行当前的异常处理程序(exception handler),异常处理程序是一块用来处理特定异常发生时的程序(当然不同异常,有不同的处理程序)。
异常处理程序执行完之后将回到主程序。要么是回到触发异常的那条指令继续执行,要么是回到触发异常指令之后的那条指令继续执行,具体视不同的异常模式而定。但有两件事,必须通过软件方式来完成:
- 之前存储的SPSR_<mode>必须恢复到CPSR
- 之前存储的LR_<mode>必须恢复到PC
当然这种操作只能在ARM状态下运行,而且通常在异常处理程序末端只需一条指令就可以完成这两件事。
- 比如执行类似如下指令:
LDMFD sp!, {r0-r12, pc}^
,这是一条比较有意思指令,它做了两件事情,除了字面上看到的从栈中恢复r0-r12,并且把LR_<mode>恢复到PC。同时指令语句最后的符号 (^)也表示把SPSR_<mode>恢复到CPSR。也是推荐的退出异常处理模式方式。 - 当然如果不需要出栈的操作,用专用的异常还回指令 (即目的寄存器是PC并且操作码后面加S)比如
MOVS pc, lr
也可以一条指令同时实现把LR_<mode>恢复到PC,把SPSR_<mode>恢复到CPSR。
备注:大多数的ARM处理器核都具有类似异常处理流程,但也会有一些差异,具体可以参考各个版本的技术参考手册。
异常向量表
如下图所示,一般来说从0x00000000开始到0x0000001C这一段地址是预留给异常向量表的, 每个异常向量在内存中都有固定地址(当然有的处理器可以选择把向量表放到高位地址)。
如上图所示,每个异常向量入口地址只能存储一条32位的指令,这条指令需要改变当前PC内容来跳转到真正的异常处理程序的地址(FIQ处理程序可以直接在向量入口地址执行,因为之后没有其他向量了,不会造成覆盖其他向量内容)。通常有下面三种方式来改变PC内容:
- 通过分支指令B。通过分支指令可以直接跳转到异常处理程序的地址,使用该方式最大能跳转的范围是32M,因此如果跳转超过32M的范围,就需要使用其他方式。
- 使用MOV指令。当然使用该方式也是有限制的,可以参考我之前的文章 ARM汇编中立即数的加载
e.g.MOV PC, #0xEF000000
- 使用LDR指令。e.g.
LDR PC, [PC + offset]
下图是参考资料中异常向量入口所映射跳转到异常处理程序的一个例子。
异常处理程序
异常处理程序(除了reset)通常都需要做现场保护、恢复工作。这里的现场指的是进入异常之前的处理器相关的一些状态,寄存器资源等(包括通用寄存器及特定寄存器)。当然有些处理器已经帮你自动进行保存了,这部分不需要进行保存。一个原则是你在这一块异常处理程序块中会破坏哪些资源,你就需要去保护哪些资源。通常现场保护是进行入栈操作。现场恢复就是,就是让处理器恢复到进入异常之前状态继续运行(就像没有发生过异常一样),与之对应的是出栈操作去恢复寄存器资源的数据,处理器的状态等。
reset异常处理
这里主要讲从软件角度来看要处理哪些事情(至于硬件处理过程可以参考ARM处理器的Programmers’ Model。即PC指到 0x00000000地址,并开始处理第一条指令,通常这是一个跳转指令,会跳转到真正的reset异常处理程序去执行。一般需要处理如下事情:
- 建立异常向量表
- 初始化内存系统(如MMU/MPU)
- 初始化所以要用到的处理器模式的堆栈,及寄存器等。
- 初始化关键I/O设备,外设寄存器,控制寄存器,时钟
- 使能中断
- 改变处理器模式/状态等。
- 跳转到主程序
备注:reset异常处理程序,并没有还回机制,最后一条指令执行完,跳转到主程序执行。
中断异常
ARM中断有2种,FIQ、IRQ。FIQ是中断里面速度最快,优先级最高的。高优先级的具体表现形式是:当同时发生中断时,FIQ最先被处理,同时禁止IRQ中断,只有FIQ退出时,IRQ才有机会被服务。
软中断异常
软中断是通过调用软中断指令SVC故意去触发的,而不是随机触发的,通过调用SVC指令来使处理器强制进入特权的Supervisor模式。SVC的语法格式如下:
SVC{cond} #imm
imm的取值范围:
- A32:0 ~2的24次方-1
- T32:0-255
imm其实对处理器来说没什么意义,但是它可以作为一个输入参数提供给软中断异常处理程序。软中断异常处理程序可以根据这个imm来判断是什么服务请求(这个程序设计者可以自己定义)。
软中断异常处理程序要获取这个imm值还是有点小麻烦(现在的ARM处理器机制,没法把imm直接传递给软中断异常处理程序),软中断异常处理程序需要自己定位到调用SVC指令的地址,并从中扣出imm值在来使用。具体步骤如下:
- 软中断异常处理程序需要通过SPSR中的T位来确认进入异常状态前的处理器是处于ARM还是Thumb状态
- 定位SVC指令。如果是之前是ARM状态则SVC指令位置在 <LR-4>,如果是Thumb状态则为 <LR-2>
- 取出SVC指令中的imm值,并通过该值来决定要调用哪个服务。
异常优先级
异常优先级是用来处理多个不同类型的异常同时发生时,谁该优先进行处理的排序。这个可以参考ARM处理器的Programmers’ Model
参考资料
【1】ARM Assembly Language_ Fundamentals and Techniques (2nd ed.)
【2】ARM嵌入式系统开发:软件设计与优化
【3】DUI0801I_armasm_user_guide
【4】ARM_SoC体系结构