14 IMX6ULL裸机开发:异常处理(基础概念)

创建时间:2022/1/19 11:04
更新时间:2022/3/6 14:23
作者:gi51wa2j
标签:100ask_IMX6ULL_v11, bingo, 正文

一、ARM架构中异常与中断的处理

重温CPU寄存器相关知识:ARM处理器程序运行的过程|ARM架构简单介绍(寄存器组|RISC与CISC)
了解A7中断与M3/4中断的区别:ARM架构中异常与中断的处理简要阐述(M3/4与A7处理器中断的区别)

二、异常处理深入分析—保存现场

2.1 处理流程简述

CPU每执行完一条指令都会检查有无中断/异常产生,发现有中断/异常产生,开始处理:

对于不用的处理器,具体的处理工作有差别:

不管是硬件还是软件实现,第一步都是保存现场

2.2 为什么要保存现场

举例:一个程序正常运行时过程如下图所示,但是这个程序随时会被异常打断,如何保证异常处理完后,被打断的程序还可以正常运行?


保存现场的目的是为了使得被中断的程序在中断恢复之后仍然可以正常运行,其中的数据无法被删改。

     内存保持不变,这很容易实现,程序不越界就可以。 所以,关键在于R0、R1、程序状态寄存器要保持不变(当然不止这些寄存器):


2.3 保存现场的过程(调用者和被调用者保存的寄存器)

ARM处理器中有这些寄存器
在arm中有个ATPCS规则(ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)。
约定R0-R15寄存器的用途
     还有一个程序状态寄存器,对于M3/M4它被称为XPSR,对于A7它被称为CPSR,我们简称为PSR。
     R0-R15、PSR,就是所谓的现场
      发生异常/中断后,在处理异常/中断前,需要保存现场难道需要保存所有这些寄存器吗?
      不需要! 在C函数中,可以修改R0-R3、R12、R14(LR)以及PSR。如果C函数要用到这些寄存器,就要把它们保存到栈里,在函数结束前在从栈中恢复它们。
     这些寄存器被拆分成2部分:调用者保存的寄存器(R0-R3,R12,LR,PSR)(硬件保存)被调用者保存的寄存器(R4-R11)(软件保存)。 比如函数A调用函数B,
对于函数A,函数A应该知道:

对于函数B:

假设函数B就是异常/中断处理函数,如果函数B本身能保证R4-R11不变,那么保存现场时,只需要保存这些:

2.4 对于M3/M4

2.4.1 硬件保存现场

2.4.2 然后调用C函数

    C函数执行完后,它返回LR所指示的位置。难道把LR设置为被中断的程序的地址就行了吗?如果只是返回LR所指示的地方,硬件帮我们保存在栈里的寄存器,怎么恢复?
    M3/M4在调用异常处理函数前,把LR设置为一个特殊的值,转给特殊的值被称为EXC_RETURN。
    当PC寄存器的值等于EXC_RETURN时,会触发异常返回机制,简单地说:会从栈里恢复R0-R3,R12,LR,PC,PSR等寄存器。EXC_RETURN的值,请参考ARM Cortex-M3与Cortex-M4权威指南.pdf,截图如下:
注意:M3/M4有两个栈,主栈和进程栈,我们常常使用的是主栈

2.5 对于A7

     处理器有9中模式:User、Sys、FIQ、IRQ、ABT、SVC、UND、MON、HYP。 上图中深色的寄存器,表示该模式下的"Banked"寄存器,比如SPSR寄存器,在很多模式下都有自己的、单独的寄存器。 比如IRQ模式下访问SPSR时,访问到的是IRQ模式下自己的SPSR_irq,别的模式下无法访问SPSR_irq。

     比较值得关注的是FIQ模式,名为"快中断",它有很多"Banked"寄存器:R8-R12,SP,LR。 在FIQ模式下,它既然能使用自己的R8-R12,SP,LR,自然不需要去保存被中断的程序的"R8-R12,SP,LR"了。 省去保存这几个寄存器的时间,处理中断时自然就快很多,所以被称为"FIQ"。

     从上图也看到,几乎每个模式下都有自己是SP寄存器,意味着这些模式下有自己的栈。


当发生异常时,以IRQ为例:

所以发生异常/中断时,在保存现场时,只需要保存:

三、CPU模式(Mode)_状态(State)与寄存器

配合这一节笔记来查看:ARM处理器程序运行的过程|ARM架构简单介绍(寄存器组|RISC与CISC)
主要来讲CPU的工作模式(Mode) 状态(State)寄存器,主要是cortex A7。 参考:

ARM9和cortex A7的CPU模式、状态、寄存器,以及发生异常时的处理细节,几乎是一模一样的。

3.1CPU有9种Mode

     跟ARM9相比,多了2中Mode:Monitor、Hyp。在文中不涉及这两种模式。 除usr模式外,其他模式是特权模式。 usr模式下,无法通过修改CPSR寄存器进入其他模式。 在其他模式下,可以通过修改CPSR寄存器进入其他模式。

3.2 芯片有两种State

以前的ARM9芯片,支持两种指令集:

对于Cortex A7芯片,还有Thumb2指令集,支持16位、32位指令混合编程。

这个字节数指一个汇编指令转为机器码时占据几个字节

3.3 ARM A7寄存器介绍

有这些寄存器:

3.3.1 寄存器总图

我们不关心Monitor模式、Hyp模式,那么可以看ARM9手册上的这个图,更直观:

3.3.2 备份寄存器

    上图中阴影部分,是该模式下自己专有的寄存器。比如执行这样的指令
mov R0, R8
     在System 模式下访问的是R0和R8,在所有模式下访问R0都是同一个寄存器。但是在FIQ模式下,访问R8时访问,它对应FIQ模式专属的R8寄存器,不是同一个物理上的寄存器,相当于:
mov R0,R8_fiq
     在这各种异常模式下,都有自己专属的R13、R14寄存器:
     为什么快中断(FIQ)有那么多专属寄存器? 回顾一下中断的处理过程:

     假设程序正在系统模式/用户模式下运行, 当发生中断时,需要把R0 ~ R14这些寄存器全部保存下来。 但如果是快中断,那么就不需要保存系统/用户模式下的R8 ~ R12这几个寄存器, 因为在FIQ模式下有自己专属的R8 ~ R12寄存器, 这样可以省略保存寄存器的时间,加快处理速度。

3.3.3 程序状态寄存器

CPSR,表示当前程序状态寄存器,这是一个特别重要的寄存器SPSR,用来保存CPSR,它们格式如下
    表示当前CPU处于哪一种模式(Mode)我们可以读取这5位来判断CPU处于哪一种模式,也可以修改它进入其他模式。
注意:假如当前处于用户模式下,是没有权限修改这些位的。M4 ~ M0对应模式,如下图所示:
cmp R0, R1 beq xxx

    如果R0 等于 R1,第1条指令会导致CPSR中的Z位等于1。后面的跳转指令,会根据Z位的值决定是否跳转:Z等于1就跳转,否则就不跳转。    
    每个模式下都有自己的SPSR寄存器,表示发生异常时,这个寄存器会用来保存被中断的模式下的CPSR。就比如程序在系统模式下运行,当发生中断时会进入irq模式时,这个SPSR_irq就保存系统模式下的CPSR。

3.4 发生异常时处理流程

发生异常时CPU如何协同工作的

3.4.1 异常向量表

在ARM9里,异常向量表基地址只有两个取值:0、0xFFFF0000。
对于cortex A7,它的异常向量表基地址是可以修改的。

3.4.2 进入异常的处理流程(硬件)

我们来翻译一下: 发生异常时,我们的CPU会做什么事情
  1. 硬件确定要进入哪种异常模式

  2. LR寄存器被更新,它表示处理完异常后要返回到哪里,这个值可能需要修改。

  3. SPSR = 被中断时的CPSR

  4. 对于"Security Exceptions",……,本课程不涉及

  5. 更新异常模式下的CPSR:设置模式位、设置mask bit(屏蔽其他异常)、设置指令集状态位

  6. PC = 异常入口地址

  7. 从PC所指示地方执行程序

3.4.3 退出异常

ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf中,对异常的退出,描述得很复杂。但是很多情况,我们并不涉及。
所以我们参考S3C2440A_UserManual_Rev13.pdf,它描述得更清晰、简单。
从异常中退出,要做什么事情?
  1. 让LR减去某个值,然后赋值给PC(PC = 某个异常LR寄存器减去 offset)

     减去什么值呢?

也就是我们怎么返回去继续执行原来的程序,根据下面这个表来取值:

如果发生的是SWI可以把 R14_svc复制给PC,

如果发生的是IRQ可以把R14_irq的值减去4赋值给PC

  1. 把CPSR的值恢复(CPSR 值等于 某一个一场模式下的SPSR)

  2. 清中断(如果是中断的话,对于其他异常不用设置)

3.4.4 确定异常返回地址

发生异常时,LR寄存器里保存的值是什么?
LR = Preferred return address + offset
"Preferred return address"是什么?请看下图:
那一条导致异常的指令的地址
offset是什么?请看下图:
根据不同指令集和不同的异常选择相应的offset
从异常中返回时,LR可能需要调整,再赋给PC。ARM9的手册讲得比较清楚,返回指令如下:

四、ARM_Thumb指令集程序示例

ARM如何告诉编译器代码所使用的指令集