目录
一、概述
中断核心处理流程如下。
- 保存现场
- 处理异常
- 恢复现场
1. 保存现场?什么是现场?现场包括什么?
在裸机程序中,执行程序,每条程序会分为若干条指令执行,每执行一次指令,会检查一次异常触发(包括中断),那么当一条语句执行到某个指令后,异常触发了,那么这条语句不能被毁掉呀,那么我们就保存此时正在运行程序的寄存器!这便是我们要保护的现场。那么现场包括什么?需要保存哪些现场呢?
说到这里,就要明白ARM架构所遵循的C语言调用标准-AAPCS(ARM架构过程调用标准)。根据这份标准,C函数可以修改R0-R3,R12,R14(LR),PSR。若C函数需要使用R4-R11,就应该将这些寄存器保存到栈空间中,并在函数结束前将他们恢复。
我们先来介绍下这几个寄存器,如下图所示。
-
- R0-R12:称为通用寄存器,前8个寄存器(R0-R7)被低寄存器,由于16位指令有限,只能访问低寄存器。高寄存器(R8-R12)则可以用于32位指令和几个16位指令。
- R13(栈寄存器SP):物理上存在两个寄存器,主栈指针(MSP)和进程栈指针(PSP)。在裸机程序中,运行正常程序使用进程栈(PSP),当处理中断时必定会使用MSP。在OS系统中,应用任务的栈都是相互独立的,在中断以及复位流程中,会使用MSP,在执行所有任务中,所使用的会是PSP。
- R14(链接寄存器LR):LR寄存器一般用于函数或子程序调用返回地址的保存。在函数或子程序结束时,程序控制将LR的数值加载到程序计数器(PC)中返回调用程序处并继续执行。执行调用后,LR的数值会自动更新。通俗一点讲,当函数调用另一个函数或子程序时,会保存LR的数值到栈中,防止丢失。在异常处理期间,LR会被自动更新为特殊寄存器(EXC_RETURN)(后续会讲),之后会在异常处理结束时,会触发异常返回,恢复对应寄存器。LR的第0位为可读可写的,LR第0位置1以表示Thumb状态
- R15 (程序计数器PC):程序寄存器是可读可写的,读操作返回当前指令地址+4。写PC会引起跳转操作。在使用一些跳转/读寄存器指令更新PC时,需要将新PC值的LSB置1以表示Thumb状态。
我们介绍完相应寄存器后,再来介绍下AAPCS(ARM架构过程调用标准),约定R0-R15寄存器的用途。
- R0-R3 调用者和被调用者之间传递参数,一般函数调用传入参数不超过四个,如果超过四个,则会用压栈的方式传参。
- R4-R11 函数可能被使用,所以在函数入口保存它们,在函数的出口恢复它们。
- xPSR状态寄存器 用于存储应用状态
那么寄存器R0-R15、xPSR这些寄存器就被称为现场。那么这些寄存器又被拆为了两部分:
- 调用者保存的寄存器(R0-R3、R12、LR、PSR)
- 被调用者保存的寄存器(R4-R11)
比如函数A调用函数B,函数A应该知道:
- R0-R3是用来传参数给函数B的
- 函数B可以肆意修改R0-R3
- 函数A不要指望函数B帮你保存R0-R3
- 保存R0-R3,是函数A的事情
- 对于LR、PSR也是同样的道理,保存它们是函数A的责任
对于函数B:
- 我用到R4-R11中的某一个,我都会在函数入口保存、在函数返回前恢复
- 保证在B函数调用前后,函数A看到的R4-R11保存不变
假设函数B就是异常/中断处理函数,函数B本身能保证R4-R11不变,那么保存现场时,只需要保存这些:
- 调用者保存的寄存器(R0-R3,R12,LR,PSR)
- PC
于是我们就知道了保存现场就是保存哪些寄存器了。如果还不懂?没关系,后续还会介绍到。那么保存现场搞定了,处理异常怎么处理的?恢复现场又是怎么恢复的呢?
2. 怎么处理异常?我们先来简单介绍下。
先简单介绍操作模式,处理模式和线程模式。
处理模式:执行中断服务程序时(ISR)等异常处理,当前属于处理模式,且处理模式总是处于特权访问等级。
线程模式:在执行普通的应用程序代码时,处理器可以处于特权访问等级,也可以处于非特权访问等级。实际的访问等级由特殊寄存器CONTROL(本章暂不做介绍)
当我们知道了有两种操作模式,那么我们在中断服务程序中的时候,必定处于处理模式。且在前面的介绍中得知处理模式一定使用MSP主栈指针。处理器也处于特权访问等级。这是一些特性知识,我们暂且记住。
3. 又怎么恢复现场?
对于ARM Cortex-M处理器,异常返回机制由一个特殊的地址EXC_RETURN触发,该数值在异常入口处被存储在链接寄存器(LR)中。当该数值由某个异常返回指令写入PC时,它就会触发异常返回流程。硬件保存现场,软件触发恢复现场异常,硬件恢复现场。
通俗一点讲,在异常处理的结尾,程序代码执行的返回会引起EXC_RETURN数值被加载到程序计数器中(PC),并触发异常返回。也就是将进入异常期间被压入栈中的寄存器数值恢复,也叫出栈。
4. 异常进入流程(核心流程)
- 多个寄存器和返回地址被压入当前使用的栈。若处理器处于线程模式且正在使用进程栈指针(PSP),则PSP指向的栈区域就会用于该压栈的过程,否则就会使用主栈指针(MSP)指向的栈区域。
- 取出异常向量(异常处理/ISR的起始地址)。因为采用哈佛总线架构,降低了中断等待时间,取出异常向量和压栈操作会同时执行,预计12个时钟周期。
- 取出待执行异常处理的指令,确认异常起始地址后,指令会被取出。
- 更新多个NVIC寄存器和内核寄存器,包括挂起状态和异常的活跃状态,包括PSR、LR、PC、SP等。根据压栈实际使用的栈,在异常处理开始前,MSP或PSP会自动调整。PC会被更新为异常处理的起始地址,而链接寄存器(LR)则会被更新为EXC_RETURN的特殊值。该寄存器为32位,高27位为1,低5位用于保存异常流程状态信息。用于异常返回。
二、保存现场
如下图,栈帧,也就是需要压入的寄存器。保存现场就是进行压栈操作,压栈流程会将寄存器压入栈中并组织栈帧。
如下图,压栈和取向量操作是同时进行的,因为Cortex-M3和Cortex-M4处理器具有多个总线接口,在压栈的同时(系统总线),处理器可以开始取向量(一般通过I-CODE总线)和取值。这样,压栈和对Flash存储器访问可以同时进行,这是基于哈佛总线架构的缘故,大大降低了中断等待时间。
如下图,压栈期间的栈访问顺序和栈帧中的寄存器顺序不同,例如Cortex-M3会在其他寄存器前首先将PC和xPSR压栈,这样在取向量时会尽快更新PC。
处理器在运行的过程中处于线程模式,只有当触发异常(包括中断)的时候,才会进入处理模式。那么在线程模式中,我们会使用MSP或PSP栈,但是当进入处理模式,我们一定会使用MSP主栈指针操作,这是一种特性。
如下图,使用主栈的线程模式的异常栈帧
如下图,使用进程栈的线程模式的异常栈帧,以及使用主栈的嵌套中断压栈。
由此我们就知道了压栈和取向量是如何同时进行的。
三、恢复现场
1、EXC_RETURN
前文看到了返回异常方式主要围绕EXC_RETURN特殊值,本次简单介绍下。当处理器进入异常处理或中断服务程序(ISR)时,链接寄存器(LR)的数值会被更新为EXC_RETURN数值。我们可以允许某个异常指令写入PC中,它会触发返回流程,也就是讲进入异常期间被压入栈中的寄存器数值恢复到对应寄存器组。
由于EXC_RETURN机制,函数一般不会返回到地址0xF0000000~0xFFFFFFFF,当我们设置这个区间的值并通过PC触发返回的时候,处理器就会明白这是异常返回命令。
我们来看一下EXC_RETURN的位域,对应不同的情况,位域也会是不同的。
通俗一点讲,介绍了触发返回的指令以及不同特殊值的功能,如下内容会介绍。
2、恢复现场
由上图(位域)得知,EXC_RETURN数值的第2位为确认提取栈帧所用的栈指针。当第2位为0时,我们就知道压栈之前使用的是主栈(MSP),当第2位等于1时,我们就知道压栈之前使用的是线程栈(PSP)。
如下图,我们分析下,在中断之前,处于线性模式,使用主栈。触发中断时,LR被设置成0xFFFFFFF9,根据位域,我们得知第0和1位固定,因为使用主栈,则第二位为0。因为返回线程模式,故第三位为1。由此得知后四位为1001,故0xFFFFFFF9。
那么在高优先级触发之前,我们处于中断服务程序中(ISR)。我们当前模式为处理模式,使用主栈,根据位域,第三位会变为0,那么后四位为0001,故0xFFFFFFF1。
那么在线程模式使用线程栈的操作中,也是类似的,这里不过多介绍。如下图,执行异常返回图。
我们知道了,恢复现场的核心操作就是将LR设置成对应的EXC_RETURN特殊值,并通过PC寄存器进行触发。
四、异常处理优化
在处理中断过程中,会有一些突发情况,比如高优先级中断抢占,出栈过程中又来了中断,那么怎么操作?我们介绍三种优化方式。分别为:末尾连锁、延迟到达、出栈抢占。
1、末尾连锁
当某个异常产生时,处理器正在处理同等优先级或高优先级的异常,该异常就会进入挂起状态。在处理器处理完当前异常后,它可以继续执行挂起的异常/中断。它并不会进行出栈以及入栈操作,而是直接进入挂起异常的异常处理。这样极大的减少了中断等待周期,大约需要6个时钟周期。
2、延时到达
当异常产生时,处理器会接收异常请求并开始压栈操作。若在压栈期间产生了更高优先级的异常,则更高优先级的后到异常会首先得到服务。通俗一点讲,在压栈过程中来了更高优先级的异常,那么直接取高优先级的向量进行处理。
3、出栈抢占
若刚处理完某个异常中断,且在出栈的过程中又触发了一个异常。那么处理器会舍弃出栈操作,并开始取向量以及下一个异常服务的指令。该优化被称作出栈抢占。
五、总结
本文介绍了中断的核心处理过程,主要分为个核心:保存现场->处理异常->恢复现场。同时还介绍了处理器在处理异常的时候如何进行优化的。