韦东山第一期学习笔记——异常与中断

说异常中断前,就有必要说说ARM的七种模式,因为在发生异常或者中断的时候会进入ARM的其中一个模式进行处理。

一、ARM七种工作模式

  • 用户模式(USR):正常程序执行模式,不能直接切换到其他模式
  • 系统模式(SYS):运行操作系统的特权任务,与用户模式类似,但具有可以直接切换到其他模式等特权
  • 快中断模式(FIQ):支持高速数据传输及通道处理,FIQ异常响应时进入此模式
  • 中断模式(IRQ):用于通用中断处理,IRQ异常响应时进入此模式
  • 管理模式(SVC):操作系统保护模式,系统复位和软件中断响应时进入此模式(由系统调用执行软中断SWI命令触发)
  • 中止模式(ABT):用于支持虚拟内存和/或存储器保护,在ARM7TDMI没有大用处
  • 未定义模式(UND):支持硬件协处理器的软件仿真,未定义指令异常响应时进入此模式

特权模式

除用户模式外,其它模式均为特权模式(Privileged Modes)。ARM 内部寄存器 和一些 片内外设 在硬件设计上只允许(或者可选为只允许)特权模式下访问。此外,特权模式可以自由的切换处理器模式,而用户模式不能直接切换到别的模式

异常模式

特权模式中除系统(system)模式之外的其他5种模式又统称为异常模式。它们除了可以通过在特权下的程序切换进入外,也可以由特定的异常进入。比如:①硬件产生中断信号进入中断异常模式,②读取没有权限数据进入中止异常模式,③执行未定义指令时进入未定义指令中止异常模式。其中管理模式也称为超级用户模式,是为操作系统提供软中断的特有模式,正是由于有了软中断,用户程序才可以通过系统调用切换到管理模式

其中模式的详细介绍

(1)用户模式:

  • 用户模式是用户程序的工作模式,它运行在操作系统的用户态,它没有权限去操作其它硬件资源,只能执行处理自己的数据,也不能切换到其它模式下,要想访问硬件资源或切换到其它模式只能通过软中断或产生异常。

(2)系统模式:

  • 系统模式是特权模式,不受用户模式的限制。用户模式和系统模式共用一套寄存器,操作系统在该模式下可以方便的访问用户模式的寄存器,而且操作系统的一些特权任务可以使用这个模式访问一些受控的资源。

  • 说明:用户模式与系统模式两者使用相同的寄存器,都没有SPSR(Saved Program Statement Register,已保存程序状态寄存器),但系统模式比用户模式有更高的权限,可以访问所有系统资源。

(3)一般中断模式:

  • 一般中断模式也叫普通中断模式,用于处理一般的中断请求,通常在硬件产生中断信号之后自动进入该模式,该模式为特权模式,可以自由访问系统硬件资源。

(4)快速中断模式:

  • 快速中断模式是相对一般中断模式而言的,它是用来处理对时间要求比较紧急的中断请求,主要用于高速数据传输及通道处理中。

(5)管理模式(Supervisor,SVC) :

  • 管理模式是CPU上电后默认模式,因此在该模式下主要用来做系统的初始化,软中断处理也在该模式下。当用户模式下的用户程序请求使用硬件资源时,通过软件中断进入该模式。

  • 说明:系统复位或开机、软中断时进入到SVC模式下。

(6)中止模式:

  • 中止模式用于支持虚拟内存或存储器保护,当用户程序访问非法地址,没有权限读取的内存地址时,会进入该模式,linux下编程时经常出现的segment fault通常都是在该模式下抛出返回的。

(7)未定义模式:

  • 未定义模式用于支持硬件协处理器的软件仿真,CPU在指令的译码阶段不能识别该指令操作时,会进入未定义模式。

二、ARM37个寄存器

搬砖文章

  • ARM总共有37个寄存器,但是每种模式下最多只能看到18个寄存器,其他寄存器虽然名字相同但是在当前模式不可见。所以有些寄存器只有切换到了该模式才可以进行操作使用

  • ARM处理器共有37个寄存器。这37个寄存器按其在用户编程中的功能划分,可分为2类寄存器,即31个通用寄存器和6个状态寄存器。这6个状态寄存器分别为:CPSR、SPSR_svc、SPSR_abt、SPSR_und、SPSR_irq和SPSR_fig

  • 37个通用寄存器是没有地址的,我们通过寄存器的名字来访问,而特殊功能寄存器有地址。

在这里插入图片描述
1)ARM共有37个寄存器,都是32位长度。搬砖文章

  • 这里的黑色表示都是同一个寄存器,都可以共用的,但是不同颜色的就说明,这是每个对应模式的专属寄存器,就是特定的寄存器。

  • 对R13这个名字来说,在ARM中共有6个名叫R13(又叫sp)的寄存器,寄存器R13在ARM指令中常用作堆栈指针SP,但这只是一种习惯用法。由于处理器的每种运行模式均有自己独立的物理寄存器R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的R13,使其指向该运行模式的栈空间。这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行。

  • R14称为子程序链接寄存器LR(Link Register),当执行子程序调用指令(BL)时,R14可得到R15(程序计数器PC)的备份。在每一种运行模式下,都可用R14保存子程序的返回地址,当用BL或BLX指令调用子程序时,将PC的当前值复制给R14,执行完子程序后,又将R14的值复制回PC,即可完成子程序的调用返回。以上的描述可用指令完成。

  • 程序计数器PC(R15),寄存器R15用作程序计数器(PC),在ARM状态下,位[1:0]为0,位[31:2]用于保存PC,在Thumb状态下,位[0]为0,位[31:1]用于保存PC,由于ARM体系结构采用了多级流水线技术,对于ARM指令集而言,PC总是指向当前指令的下两条指令的地址,即PC的值为当前指令的地址值加8个字节程序状态寄存器。PC为程序指针,PC指向哪里,CPU就会执行那条指令。整个CPU只有一个PC(CPOSR也只有一个,但SPSR有5个)

  • 寄存器R16用作CPSR(CurrentProgram Status Register,当前程序状态寄存器),CPSR可在任何运行模式下被访问,它包括条件标志位、中断禁止位、当前处理器模式标志位,以及其他一些相关的控制和状态位。CPSR用来记录当前cpu的状态,因此只有一个

  • 每一种运行模式下又都有一个专用的物理状态寄存器,称为SPSR(Saved Program Status Register,备份的程序状态寄存器),当异常发生时,SPSR用于保存CPSR的当前值,从异常退出时则可由SPSR来恢复CPSR。

  • 由于用户模式和系统模式不属于异常模式,它们没有SPSR,当在这两种模式下访问SPSR,

CPSR程序状态寄存器

百度百科:CPSR(当前程序状态寄存器)在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。每一种处理器模式下都有一个专用的物理状态寄存器,称为SPSR(备份程序状态寄存器)。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常中断模式,所以他没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。

搬砖文章
在这里插入图片描述
(1)条件码标志

N、Z、C、V,最高4位称为条件码标志。ARM的大多数指令可以条件执行的,即通过检测这些条件码标志来决定程序指令如何执行。

各个条件码的含义如下:

N:在结果是有符号的二进制补码情况下,如果结果为负数,则N=1;如果结果为非负数,则N=0。

Z:如果结果为0,则Z=1;如果结果为非零,则Z=0。

C:其设置分一下几种情况:

   对于加法指令(包含比较指令CMN),如果产生进位,则C=1;否则C=0。

   对于减法指令(包括比较指令CMP),如果产生借位,则C=0;否则C=1。

   对于有移位操作的非法指令,C为移位操作中最后移出位的值。

   对于其他指令,C通常不变。

V:对于加减法指令,在操作数和结果是有符号的整数时,如果发生溢出,则V=1;如果无溢出发生,则V=0;对于其他指令,V通常不发生变化。

说那么啰嗦也看不懂,意思就是说假若,执行cmp复制指令,如果相等则第30位也就是Z位就为1,执行beq跳转指令时是判断第30位的数值是否等于1,选择跳转与否——韦东山老师说的,其他理解也不太懂,这个标志位理不理解都无所谓了,需要用的时候再说好了

(2)控制位

  • 控制位的低五位M0-M4,是控制位中的模式位。用来选择模式,就是我们进入的是那个特殊模式,这里有七种模式可以选择。
  • I、F、T位中的I位是用来设置那种工作状态,有两种工作状态可以选择,我们一般默认使用的是ARM。F位控制FIQ的控制位,T位是控制IRQ的控制位。

三、 ARM架构异常中断介绍

搬砖文章当异常中断发生时,系统执行完当前指令就会跳转到响应的异常中断处理程序,当处理程序执行完毕后,程序返回到发生中断的指令的下一条指令执行;在进入异常中断处理程序时,要保存被中断的执行现场,在异常中断处理程序退出时,要恢复被中断的程序的执行现场

关于ARM的PC指针(什么时候PC+8,PC+4,PC-4,PC-8)可以看看这篇文章,里面讲了我们中断异常,在异常返回地址时应该怎么用pc

ARM体系中异常中断种类

  • 复位(Reset):当处理器的复位引脚有效时,系统产生复位异常中断,程序跳到复位异常中断处理程序执行。复位异常中断通常用在下面两种情况:系统加电时,系统复位时,跳转到复位中断向量处执行,成为软复位

  • 未定义指令(undefined):当ARM处理器或者是系统中的协处理器认为当前指令未定义时,产生未定义的指令异常中断。可以通过异常中断机制仿真浮点向量运算

  • 软中断(software interrupt):这是一个由用户定义的中断指令。可用于用户模式下的程序调用特权操作指令。在实时操作系统(RTOS)中可以通过该机制实现系统功能调用。

  • 指令预取中止:如果处理器预取的指令的地址不存在,或者该地址不允许当前指令访问,当该被预取的指令执行时,处理器产生指令预取中止异常中断。

  • 数据访问中止:如果数据访问指令的目标地址不存在,或者该地址不允许当前指令访问,处理器产生数据访问中止异常中断。

  • 外部中断请求(IRQ):当处理器的外部中断请求引脚有效,而且CPSR寄存器中的I控制位被清除时,处理器产生外部中断请求异常中断。系统中各个外设通常通过该异常中断请求处理器服务。

  • 快速中断请求(FIQ):当处理器的外部快速中断请求引脚有效,而且CPSR寄存器中的F控制位被清除时,处理器产生外部中断请求(FIQ)异常中断。

异常处理向量表及异常中断优先级

中断异常向量表指定了异常中断及其处理程序的对应关系。它通常存放在存储地址的低端。在ARM体系中,异常中断向量表大小为32个字节。其中,每个异常中断占据4个字节大小,保留4个字节空间

每个异常中断对应的中断向量表中4个字节的空间中存放了一个跳转指令或者一个向pc寄存器中赋值的数据访问指令。通常这两种指令,程序将调转到相应的异常中断处理程序处执行

当几个异常中断同时发生时,就必须按照一定的次序来处理这些异常中断,在ARM中通过异常中断赋予一定的优先级实现这种处理次序。当然有异常中断时不可能同时发生的,如指令预取中止异常和软中断(SWI)异常中断时由同一条指令的执行触发的,他们时不可能同时发生的。处理器执行某个特定的异常中断的过程中,成为处理器出去特定的中断模式。各异常中断的中断向量地址以及中断的处理优先级

中断向量地址异常中断类型异常中断模式优先级
0x0复位特权模式(SVC)1
0x4未定义指令未定义指令中止模式6
0x8软中断(SWI)特权模式(SVC)6
0xC指令预取中止中止模式5
0x10数据访问中止中止模式2
0x14保留未使用未使用
0x18外部中断请求外部中断模式4
0x1C快速中断请求快速中断模式3

ARM中断处理机制

ARM有七种异常中断类型,优先级、工作模式(有七种工作模式)、地址、功能都不一样。如其中软件中断SWI优先级为6,工作模式管理模式,异常向量地址为0x00000008,功能是用户定义的中断指令,可用于用户模式下的程序调用特权操作。

当中断产生后,除了复位中断立即中止当前指令外,其余情况都是处理器完成当前指令后,才去执行异常处理程序。

ARM处理器有多种processor mode,例如user mode(用户空间的AP所处于的模式)、supervisor mode(即SVC mode,大部分的内核态代码都处于这种mode)、IRQ mode(发生中断后,处理器会切入到该mode)等。对于linux kernel,其中断处理处理过程中,ARM 处理器大部分都是处于SVC mode。但是,实际上产生中断的时候,ARM处理器实际上是进入IRQ mode,因此在进入真正的IRQ异常处理之前会有一小段IRQ mode的操作,之后会进入SVC mode进行真正的IRQ异常处理。

ARM中断大致处理过程

搬砖文章

  • (1)将CPSR的值保存到将要执行的异常中断对应的各自SPSR中,以实现对处理器当前状态、中断屏蔽及各标志位的保护。

  • (2)设置当前状态寄存器CPSR的相应位。设置CPSR中的M4~M0的5位,进入相应工作模式,设置I=1禁止IRQ中断,如果进入复位模式或FIQ模式,还要设置F=1以禁止FIQ中断。

  • (3)将引起异常指令的下一条地址(断点地址)保存到新异常工作模式的LR(R14)中,使异常处理程序执行完后正确返回原来程序处继续向下执行。

  • (4)给程序计数器PC强制赋值,转入向量地址,以便执行相应的处理程序。

  • 每种中断异常模式对应两个寄存器SP和LR。

更详细的讲,linux的中断机制可以分为两部分:

  • 定义的中断处理函数是如何注册到linux系统的?
  • 当中断发生时,linux如何自动跳转并找出中断号,然后根据中断号来找到注册在该中断号上的中断处理函数并执行?

ARM中断大致返回过程

搬砖文章

从中断返回。如果是复位异常,系统自动从0x00000000开始重新执行程序,无需返回。

  • (1)首先恢复原来被保护的用户寄存器。

  • (2)将SPSR寄存器复制到CPSR中,使得原来CPSR状态从相应的SOSR中恢复,一恢复被中断的程序状态。

  • (3)根据异常类型将PC值恢复成断点地址,以继续执行用户原来运行着的程序。

  • (4)清除CPSR中的中断禁止标志I和F,开放外部中断和快速中断。

注意:

  • (1)程序状态寄存器及断点地址的恢复必须同时进行。

  • (2)由于异常随机发生,所以要对异常向量进行初始化,即在异常向量的地址处放置一条跳转指令,跳转到异常处理程序。

🌹响应复位异常中断处理过程

如下情况会导致复位:

  1. 上电复位:在上电后,复位使内部达到预定的状态,特别是程序跳到初始入口;
  2. 复位引脚上的复位脉冲:这是由外部其他控制信号引起的;
  3. 对系统电源检测发现过压或欠压;
  4. 时钟异常复位

当发生复位 时,处理器硬件响应中断,自动执行如下操作:

  1. 强制进入管理模式;
  2. 强制进入ARM状态;
  3. 禁止IRQ中断和FIQ中断;
  4. 跳转到绝对地址PC=0x00000000处执行;

🌹未定义指令异常处理过程

如下情况会导致:

  1. 遇到一条无法执行的指令,此指令没有定义;
  2. 执行一条对协处理器的操作指令,在正常情况下,协处理器应该应答,但协处理器没有应答。

处理器响应中断后,硬件自动执行下列操作 :

  1. 把程序状态寄存器CPSR拷贝给SPSR_und;
  2. 强制进入未定义模式;
  3. 强制进入到ARM模式;
  4. 禁止IRQ中断
  5. 把下一条指令的地址拷贝给LR;
  6. 跳转到绝对地址PC=0x00000004处执行;

关于从异常中断处理程序的返回:

未定义指令异常中断是由当前执行的指令自身产生的,当产生中断时,程序计数器PC的值还未更新,它指向当前指令后面的第二条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置)。当未定义指令异常中断发生时,处理器自动将值(pc-4)保存到lr_und中,此时(pc-4)指向当前指令的下一条指令,所以从未定义指令异常中断返回可以通过如下指令来实现:

MOV  PC,LR

当异常中断处理程序中使用了数据栈时,可以使用下面的指令在进入异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场。异常中断程序中使用的数据栈由用户提供。

STMFD   sp! , {reglist,lr}
....
LDMFD   sp! , {reglist,pc}^

reglist是异常中断处理程序使用的寄存器列表。标识符^指示将SPSR_mode寄存器的内容复制到CPSR中,该指令只能在特权模式下使用。

🌹软件中断异常处理过程

是由指令SWI引起的,程序在执行这一指令后,进入异常中断。处理器响应中断后,硬件自动执行下列操作 :

  1. 把程序状态寄存器CPSR拷贝给SPSR_svc;
  2. 强制进入管理模式;
  3. 强制进入到ARM状态;
  4. 禁止IRQ中断。
  5. 把下一条指令的地址拷贝给LR;
  6. 跳转到绝对地址PC=0x00000008处执行;

关于从异常中断处理程序的返回:

软件中断异常是由当前执行的指令自身产生的,当产生中断时,程序计数器PC的值还未更新,它指向当前指令后面的第二条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置)。当软件中断发生时,处理器自动将值(pc-4)保存到lr_siw中,此时(pc-4)指向当前指令的下一条指令,所以从软件中断返回可以通过如下指令来实现:

MOV PC,LR

当异常中断处理程序中使用了数据栈时,可以使用下面的指令在进入异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场。异常中断程序中使用的数据栈由用户提供。

STMFD   sp! , {reglist,lr}
....
LDMFD   sp! , {reglist,pc}^

reglist是异常中断处理程序使用的寄存器列表。标识符^指示将SPSR_mode寄存器的内容复制到CPSR中,该指令只能在特权模式下使用。

🌹预取指中止异常处理过程

由程序存储器引起的中止异常叫做预取指中止异常;
由数据存储器引起的中止异常叫做数据中止异常。

由于ARM的指令是3级流水线结构,读取指令周期是提前进行的,因此把读取指令的过程一般称预取指。在指令预取时,如果目标地址是非法的,该指令被标记成有问题的指令,这时,流水线上该指令之前的指令继续执行。有两种可能如下:

  1. 当执行这条指令前程序发生跳转,则这条无效指令不引起异常中断;
  2. 当执行到这条指令时,处理器会发生预取指中止异常,引起中断。

处理器响应中断后,硬件自动执行下列操作 :

  1. 把程序状态寄存器CPSR拷贝给SPSR_abt;
  2. 强制进入中止异常模式;
  3. 强制进入到ARM状态;
  4. 禁止IRQ中断。
  5. 把中断时PC的地址拷贝给LR;
  6. 跳转到绝对地址PC=0x0000000C处执行;

关于从异常中断处理程序的返回:

在指令预取时,如果目标地址是非法的,该指令被标记成有问题的指令,这时,流水线上该指令之前的指令继续执行,当执行到该被标记成有问题的指令时,处理器产生指令预取中止异常中断。发生指令预取异常中断时,程序要返回到该有问题的指令处,重新读取并执行该指令,因此指令预取中止异常中断应该返回到产生该指令预取中止异常中断的指令处,而不是当前指令的下一条指令。

指令预取异常是由当前执行的指令自身产生的,当产生中断时,程序计数器PC的值还未更新,它指向当前指令后面的第二条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置)。当指令预取中止异常中断发生时,处理器自动将值(pc-4)保存到lr_abt中,此时(pc-4)指向当前指令的下一条指令,所以从软件中断返回可以通过如下指令来实现:

SUBS PC,LR,#4

当异常中断处理程序中使用了数据栈时,可以使用下面的指令在进入异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场。异常中断程序中使用的数据栈由用户提供。

SUBS LR,LR,#4
STMFD sp! , {reglist,lr}.
LDMFD sp! , {reglist,pc}^

reglist是异常中断处理程序使用的寄存器列表。标识符^指示将SPSR_mode寄存器的内容复制到CPSR中,该指令只能在特权模式下使用。

🌹数据中止异常处理过程

ARM处理器访问数据存储器时,在读取数据的同时数据存储器发出了中止信号,引起数据中止异常。如果数据访问指令的目标地址不存在,或者该地址不允许当前指令访问,处理器产生数据访问中止异常中断;
处理器响应中断后,硬件自动执行下列操作 :

  1. 把程序状态寄存器CPSR拷贝给SPSR_irq;
  2. 强制进入IRQ异常模式;
  3. 强制进入到ARM状态;
  4. 禁止IRQ中断;
  5. 把中断时的PC的地址值拷贝给LR;
  6. 跳转到绝对地址PC=0x00000010处执行;

关于从异常中断处理程序的返回:
发生数据访问异常中断时,程序要返回到该有问题的指令处,重新访问该数据,因此数据访问异常中断应该返回到产生该数据访问中止异常中断的指令处,而不是当前指令的下一条指令。

数据访问异常中断由当前执行的指令在ALU里执行时产生,当数据访问异常中断发生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置)。此时处理器将值(pc-4)保存到lr_abt中,它指向当前指令后面第2条指令,所以返回操作可以通过下面指令实现:

SUBS PC, LR, #8

当异常中断处理程序中使用了数据栈时,可以使用下面的指令在进入异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场。异常中断程序中使用的数据栈由用户提供。

SUBS LR,LR,#8
STMFD   sp! , {reglist,lr}
....
LDMFD   sp! , {reglist,pc}^

reglist是异常中断处理程序使用的寄存器列表。标识符^指示将SPSR_mode寄存器的内容复制到CPSR中,该指令只能在特权模式下使用。

🌹中断请求(IRQ)异常处理过程

例如:定时器中断、串行口通讯中断、外部信号中断和A/D处理中断等。IRQ中断是可屏蔽的。在状态寄存器中的I位就是IRQ的屏蔽位。当I=1时。则屏蔽IRQ中断,当I=0时,则允许中断。处理器复位后置I为1,关闭中断。处理器响应中断后,硬件自动执行下列操作 :

  1. 把程序状态寄存器CPSR拷贝给SPSR_irq;
  2. 强制进入IRQ异常模式;
  3. 强制进入到ARM状态;
  4. 禁止IRQ中断;
  5. 把中断时的PC的地址值拷贝给LR;
  6. 跳转到绝对地址PC=0x00000018处执行;

关于从异常中断处理程序的返回:

通常处理器执行完当前指令后,查询IRQ中断引脚,并查看系统是否允许IRQ中断,如果某个中断引脚有效,并且系统允许该中断产生,处理器将产生IRQ异常中断,当IRQ异常中断产生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置),当IRQ异常中断产生时,处理器将值(pc-4)保存到IRQ异常模式下的寄存器lr_irq中,它指向当前指令之后的第2条指令,因此正确返回地址可以通过下面指令算出:

SUBS   PC,LR,#4 

当异常中断处理程序中使用了数据栈时,可以使用下面的指令在进入异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场。异常中断程序中使用的数据栈由用户提供。

SUBS LR, LR, #4
STMFD   sp! , {reglist,lr}
 ....
LDMFD   sp! , {reglist,pc}^

reglist是异常中断处理程序使用的寄存器列表。标识符^指示将SPSR_mode寄存器的内容复制到CPSR中,该指令只能在特权模式下使用。

注:为什么PC会指向当前执行指令的后12个字节?

当前指令执行时(此时PC指向当前指令后面的第2条指令),如果发生IRQ中断,ARM检测到IRQ中断后,取指和执行单元都不会改变,只有译码单元会改变,译码单元改为开始译码中断指令。执行完当前指令后,PC值加4个字节(此时PC指向刚刚那条指令后面的第3条指令),译码单元将译完的中断指令送到执行单元,执行单元执行中断指令,保存PC-4(此时PC指向当前指令后面的第2条指令)的值到LR_irq,同时跳转到IRQ中断向量处。(详细可看ARM流水线机制)

快速中断(FIQ)请求异常。

FIQ快速中断是可屏蔽的。在状态寄存器中的F位就是FIQ的屏蔽位。当F=1时。则屏蔽FIQ中断,当F=0时,则允许中断。处理器复位后置F为1,关闭中断。处理器响应中断后,硬件自动执行下列操作:

  1. 把程序状态寄存器CPSR拷贝给SPSR_fiq;
  2. 强制进入FIQ异常模式;
  3. 强制进入到ARM状态;
  4. 禁止FIQ中断;
  5. 把中断时的PC的地址值拷贝给LR;
  6. 跳转到绝对地址PC=0x0000001C处执行;

🌹快速中断(FIQ)请求异常。

FIQ快速中断是可屏蔽的。在状态寄存器中的F位就是FIQ的屏蔽位。当F=1时。则屏蔽FIQ中断,当F=0时,则允许中断。处理器复位后置F为1,关闭中断。处理器响应中断后,硬件自动执行下列操作:

  1. 把程序状态寄存器CPSR拷贝给SPSR_fiq;
  2. 强制进入FIQ异常模式;
  3. 强制进入到ARM状态;
  4. 禁止FIQ中断;
  5. 把中断时的PC的地址值拷贝给LR;
  6. 跳转到绝对地址PC=0x0000001C处执行;

关于从异常中断处理程序的返回:

通常处理器执行完当前指令后,查询FIQ中断引脚,并查看系统是否允许FIQ中断,如果某个中断引脚有效,并且系统允许该中断产生,处理器将产生FIQ异常中断,当FIQ异常中断产生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置),当FIQ异常中断产生时,处理器将值(pc-4)保存到IRQ异常模式下的寄存器lr_fiq中,它指向当前指令之后的第2条指令,因此正确返回地址可以通过下面指令算出:

SUBS   PC,LR,#4 

当异常中断处理程序中使用了数据栈时,可以使用下面的指令在进入异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场。异常中断程序中使用的数据栈由用户提供。

SUBS LR, LR, #4
STMFD   sp! , {reglist,lr}
....
LDMFD   sp! , {reglist,pc}^

reglist是异常中断处理程序使用的寄存器列表。标识符^指示将SPSR_mode寄存器的内容复制到CPSR中,该指令只能在特权模式下使用。

FIQ和IRQ补充

  • FIQ和IRQ都是用于外部设备向CPU请求中断服务,这两个异常中断的引脚都是低电平有效,都可以通过CPSR的I控制位来设置是否屏蔽;

  • FIQ比IRQ的异常中断优先级高,所以FIQ通常用于对于相应时间要求比较苛刻的任务,FIQ异常中断向量为0X1C,位于中断向量表的最后,这样FIQ异常中断程序可以字节放在地址0X1C后面,这样节省了中断向量表中的跳转指令;

  • 当系统存在cache时,可以把FIQ异常中断向量和处理程序放在一起锁定在cache中,从而大大提高相应时间,同时FIQ异常模式有额外的5个寄存器,可以有效的提高执行速度;

对于中断处理程序重入和不可重入

不可以重入的中断程序,在函数前用关键字__irq声明;不可重入的中断处理函数里会做如下操作:

  1. 保存APCS规定的被破坏的寄存器;
  2. 保存其他中断处理程序用到的寄存器;
  3. 同时将(LR - 4)赋予程序计数器PC实现中断处理程序的返回,并且恢复CPSR寄存器的内容;

缺省情况下ARM 中断是不可重入的,因为一旦进入异常响应状态,ARM 自动关闭中断使能。如果在异常处理过程中简单地打开中断使能而发生中断嵌套,显然新的异常处理将破坏原来的中断现场而导致出错。但有时候中断的可重入又是需要的,对应可重入异常中断处理程序需要做如下特别操作:

  1. 将返回地址保存到IRQ的数据栈中;
  2. 保存工作寄存器和SPSR_irq;
  3. 清除中断标志位;
  4. 将处理器切换到系统模式,重新使能中断;
  5. 保存用户模式的LR寄存器和被调用者不保存的寄存器;
  6. 调用C语言的IRQ/FIQ异常中断处理程序;
  7. 当C语言的IRQ/FIQ异常中断处理程序返回后,恢复用户模式的寄存器,并禁止中断;
  8. 切换到IRQ模式,禁止中断;
  9. 恢复工作寄存器和寄存器LR_irq;
  10. 从IRQ异常中断处理程序中返回;

四、ARM中断异常实例

此次的实例是用到韦东山老师教程的实例

und的中断异常实例

搬砖文章

1. 首先创建异常向量表

  • 异常向量表是一段特定内存地址空间,每种ARM异常对应一个字长空间(4Bytes),正好是一条32位指令长度,当异常发生时,CPU强制将PC的值设置为当前异常对应的固定内存地址。

  • 正是由于异常向量表的存在,才让硬件异常处理和程序员自定义处理程序有机联系起来。异常向量表里0x00000000地址处是reset复位异常,之所以它为0地址,是因为CPU在上电时自动从0地址处加载指令,由此可见将复位异常安装在此地址处也是前后接合起来设计的,不得不感叹CPU设计师的伟大,其后面分别是其余7种异常向量,每种异常向量都占有四个字节,正好是一条指令的大小,最后一个异常是快速中断异常,将其安装在此也有它的意义,在0x0000001C地址处可以直接存放快速中断的处理程序,不用设置跳转指令,这样可以节省一个时钟周期,加快快速中断处理时间。

ldr pc, __reset //复位0x00000000 

ldr pc, __undef //未定义指令0x00000004 

ldr pc, __svc //管理员模式0x00000008 

ldr pc, __pabort //指令异常0x0000000c 

ldr pc, __dabort //数据异常0x0000010 

ldr pc, __irp //普通中断0x00000018 

ldr pc, __firp //快速中断0x00000020 

注意: 这里用的是ldr跳转而不是b跳转,为什么??假若我们是Nand启动,我么的需要处理异常的函数在4k之外,我们重定位到了SDRAM,那么b是相对跳转就不适用了,必须要用ldr的绝对跳转

_reset: 
.word reset //分配一个字节的空间存放相应处理函数首地址,下同 

_undef: 
.word undef 

_undef: 
.word svc 

_undef: 
.word pabort 

_undef: 
.word dabort 

_undef: 
.word irp 

_undef: 
.word firp

这里的 .word 分配一段字内存单元,就是分配我们相应处理函数的地址,其中.word 后面的是函数的名字,也就是首地址

2. 保存执行现场

因为,我们sp就是r13寄存器是每种模式都有独有的寄存器,所以我们需要设置栈的操作,然后再进行保存现场
异常处理程序最开始,要保存被打断程序的执行现场,程序的执行现场无非就是保存当前操作寄存器里的数据,这些数据都是在R0-R12的寄存器里存放,所以可以通过下面的栈操作指令实现保存现场:

ldr sp, = 0x34000000//在SDRAM分配一些栈空间
stmfd sp!, {r0-r12, lr}

3. 异常处理的返回

4. 异常返回地址

恢复被打断程序运行时寄存器的数据

  • 一条汇编指令的运行有三个步骤,取指、译码、执行,当第一条汇编指令取指完成后,紧接着就是第二条指令的取指,然后第三条…如此嵌套

  • 未定义指令异常时,因为这个异常发生在指令译码阶段,所以,此时PC的值就是未定义指令加4,然后保存到LR(参考流水线图);因为该指令未定义,所以返回时就不应该返回到这条未定义指令,而是返回到它的下一条指令,R14中保存的刚好就是下一条指令的地址,所以就不用计算了,直接将R14赋值给PC就行了

mov pc, lr
模式恢复

异常发生后,进入异常处理程序时,将用户程序寄存器R0~ R12里的数据保存在了异常模式下栈里面,异常处理完返回时,要将栈里保存的的数据再恢复回原先R0~R12里。

毫无疑问在异常处理过程中必须要保证异常处理入口和出口时栈指针SP要一样,否则恢复到R0~R12里的数据不正确,返回被打断程序时执行现场不一致,出现问题,虽然将执行现场恢复了,但是此时还是在异常模式下,CPSR里的状态是异常模式下状态。

因此要恢复SPSR里的保存状态到CPSR里,SPSR是被打断程序执行时的状态,在恢复SPSR到CPSR的同时,CPU的模式和状态从异常模式切换回了被打断程序执行时的模式和状态。

此刻程序现场恢复了,状态也恢复了,但PC里的值仍然指向异常模式下的地址空间,我们要让CPU继续执行被打断程序,因此要再手动改变PC的值为进入异常时的返回地址,该地址在异常处理入口时已经计算好,直接将PC = LR即可。

上述操作可以一步一步实现,但是通常我们可以通过一条指令实现上述全部操作:

ldmfd sp!, {r0-r12, pc}^

注:SP为对应异常模式下SP,^符号表示恢复SPSR到CPSR。

总代码

直接代码

.text
.global_start

_start:
	b _reset	/* 复位0x00000000 */
	ldr pc, _undef /* 未定义指令0x00000004 */
	ldr pc, _svc /* 管理员模式0x00000008 */
	ldr pc, _pabort /* 指令异常0x0000000c */
	ldr pc, _dabort /* 数据异常0x0000010 */
	ldr pc, _irp /* 普通中断0x00000018 */
	ldr pc, _firp /* 快速中断0x00000020 */

_undef:
	.word do_und
_undef: 
	.word svc 
_undef: 
	.word pabort 
_undef: 
	.word dabort 
_undef: 
	.word irp 
_undef: 
	.word firp
/**这里的 .word分配一段字内存单元,就是分配我们相应处理函数的地址,其中.word 后面的是函数的名字,也就是首地址*/	
	
	
do_und:

	/* 硬件的四大步,硬件自动完成
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 给程序计数器PC强制赋值,转入向量地址0x04,以便执行相应的处理程序。
	 */

	/*
	 * 软件的三小步,需要我们自行来完成
	 * 1. 保存现场
	 * 2. 处理异常
	 * 3. 恢复现场
	 */ 
	 
	 /**************** 保存现场 ******************/
	/*我们要先给sp设置栈空间,因为每个模式都有特定的sp寄存器*/
	ldr sp, = 0x34000000 /*在SDRAM分配一些栈空间*/
	
	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmfd sp!, {r0-r12, lr} /*从右到左保存*/
	
	/****************** 处理异常 *****************/
	mrs r0, cpsr /*将CPSR状态寄存器读取,保存到R1中*/
	ldr r1, = und_string
	bl print_exception

	/****************** 恢复现场 *****************/
	ldmfd sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里;把lr的值赋值给pc, 从左到右提取 */
	
und_string:
	.string "undefined instruction exception"	
	

do_svc:
	nop
	
do_abt:
	nop
	
do_irp: 
	nop
	
do_firp:
	nop
	
.align 4
	
_reset:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   /* R1_nF:OR:R1_iA */
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
	
	

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	/* bl sdram_init2 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

	ldr pc, =sdram
sdram:
	bl uart0_init

	bl print1
	/* 故意加入一条未定义指令 */
und_code:
	.word 0xdeadc0de  /* 未定义指令 */
	bl print2

	/* bl main 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt

按键中断实例

搬砖文章:ARM按键中断实例

do_irq中断

do_irq:

	/* 硬件的四大步,硬件自动完成
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 给程序计数器PC强制赋值,转入向量地址0x04,以便执行相应的处理程序。
	 */

	/*
	 * 软件的三小步,需要我们自行来完成
	 * 1. 保存现场
	 * 2. 处理异常
	 * 3. 恢复现场
	 */ 


/**************** 保存现场 ******************/
	/*我们要先给sp设置栈空间,因为每个模式都有特定的sp寄存器*/
	ldr sp, = 0x33d000000 /*在SDRAM分配一些栈空间*/
	
	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	sub lr, lr, #4
	stmfd sp!, {r0-r12, lr} /*从右到左保存*/


/**************** 处理irq中断异常 ******************/
	bl handle_irq_c


/**************** 恢复现场 ******************/
ldmfd sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里;把lr的值赋值给pc, 从左到右提取 */

处理irq异常的函数




1. 初始化外部中断

步骤:

  • ①设置寄存器GPFCON,GPGCON,配置GPF0/2,GPG3/11为外部中断引脚,对应原理图的:S2,S3,S4,S5

  • ②设置寄存器GPFCON,配置GPF4/5/6引脚为输出,用于控制LED灯,对应原理图的,D10,D11,D12

  • ③设置EXTIN0,EXTIN1,EXTIN2,寄存器,设置中断引脚的触发方式为双边沿触发。

  • ④设置EINTMASK寄存器,配置外部中断11和外部中断19中断使能,而外部中断0和2是默认使能的。

  • ⑤可以通过查询EINTPEND寄存器查询某个中断是否发生,写相应位为1清除中断(可查询的位为:EINT4-EINT23)

我们使用的硬件是韦东山的JZ2440_V3单片机,我们可以看到这里有4个按键

①初始化按键引脚

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们进行按键的引脚的初始化

/*初始化按键,设为中断源*/
void key_exit_init(void){
 	/*1 配置GPFO,GPF2为中断引脚 */
	//先把eint0和eint2这两个引脚清零
	GPFCON &= ~(3<<0)|(3<<4));
	GPFCON |= ((2<<0)|(2<<4));

 	/* 配置GPG3,GPG11为中断引脚 */
	//先把eint11和eint19这两个引脚清零
	GPFCON &= ~(3<<0)|(3<<4));
	GPFCON |= ((2<<0)|(2<<4));

	/* led灯的引脚配置:配置GPF4/5/6 为输出引脚--->LED,对应D10,D11,D12 */
	GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
	GPFCON |=  ((1<<8) | (1<<10) | (1<<12)); 

}
②设置按键触发模式

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

/*初始化按键,设为中断源*/
void key_exit_init(void){

	/*设置中断的触发方式:双边沿触发*/
	EXTINT0 &= ~((7<<0) | (7<<8));
	EXTINT0 |=  ((7<<0) | (7<<8)); //设置[2:0]=0b111,[10:8]=0b111
 
	EXTINT1 &= ~(7<<12);
	EXTINT1 |=  (7<<12); //设置[14:12]=0b111
 
	EXTINT2 &= ~(7<<12);
	EXTINT2 |=  (7<<12);  //设置[14:12]=0b111
 
	/*设置EINTMASK使能外部中断11和19,外部中断0和2不用设置*/
	EINTMASK &= ~((1<<11) | (1<<19));
}
③设置EINTMASK寄存器

设置EINTMASK寄存器,配置外部中断11和外部中断19中断使能,而外部中断0和2是默认使能的。
在这里插入图片描述

/*初始化按键,设为中断源*/
void key_exit_init(void){
	/*设置EINTMASK使能外部中断11和19,外部中断0和2不用设置*/
	EINTMASK &= ~((1<<11) | (1<<19));
}
④设置EINTPEND寄存器

可以通过读取EINTPEND寄存器来读取某个中断是否发生,写相应的位为0
在这里插入图片描述

初始化按键中断的总代码
/*初始化按键中断*/
void key_eint_init(void)
{
	/*配置GPF0/2,GPG3/11,为中断引脚*/
	GPFCON &= ~((3<<0)| (3<<4));  //对位先清零
	GPFCON |=  ((2<<0)| (2<<4));	//设置[1:0]=0b10,[5:4]=0b10
	GPGCON &= ~((3<<6) | (3<<22));
	GPGCON |=  ((2<<6) | (2<<22));	//设置[7:6]=0b10,[23:22]=0b10
	/*配置GPF4/5/6 为输出引脚--->LED,对应D10,D11,D12*/
	GPFCON &= ~((3<<8) | (3<<10) 
| (3<<12));
	GPFCON |=  ((1<<8) | (1<<10) 
| (1<<12));
	
	/*设置中断的触发方式:双边沿触发*/
 
	EXTINT0 &= ~((7<<0) | (7<<8));
	EXTINT0 |=  ((7<<0) | (7<<8)); //设置[2:0]=0b111,[10:8]=0b111
 
	EXTINT1 &= ~(7<<12);
	EXTINT1 |=  (7<<12); //设置[14:12]=0b111
 
	EXTINT2 &= ~(7<<12);
	EXTINT2 |=  (7<<12);  //设置[14:12]=0b111
 
	/*设置EINTMASK使能外部中断11和19,外部中断0和2不用设置*/
	EINTMASK &= ~((1<<11) | (1<<19));
	
 
}

设置中断控制器

这个是我们的中断控制器的流程图
在这里插入图片描述

这个是我们的60个中断源
在这里插入图片描述
在这里插入图片描述
但是我们使用的外部中断寄存器可以直接到达SRCPND不需要设置SUBSRCPND和SUBMASK这两个寄存器
我们使用的外部中断源只需要设置SRCPND、 MASK、 INTPND这三个就可以

  • INTMASK 屏蔽寄存器
  • INTPND 我们可以读这个寄存器,用于显示当前优先级最高,正在发生的中断,确定是那个中断产生了
  • INTMOD默认设置,模式寄存器默认为IRQ中断。
  • SRCPND用来提示哪个中断产生了,处理完中断之后,需要清除相应位。
SRCPND寄存器
  • 描述:用来提示哪个中断产生了,处理完中断之后,需要清除相应位。
  • 我们用的是eint中断,若是需要的话设置对应的这几位就好了,不过我们这里不需要设置

在这里插入图片描述
在这里插入图片描述

INTMOD寄存器
  • 描述:默认设置,模式寄存器默认为IRQ中断。
  • 默认值就好

在这里插入图片描述
在这里插入图片描述

INTMASK寄存器
  • 描述:外部总段屏蔽寄存器,若是某一位设置为1,代表中断,默认为1。我们需要设置为0,就是不屏蔽中断源
    在这里插入图片描述
    在这里插入图片描述
INTPEND显示优先级
  • 描述:用来显示当前优先级最高的正在发生的中断。
  • 需要清除对应位

在这里插入图片描述
在这里插入图片描述

INTOFFSET寄存器
  • INTOFFSET用来显示INTPEND中哪个中断正在等待处理,显示INPEND寄存器哪一个位设置为1,比如,INTPAD中bit0等于1的话INTOFFSET就等于0 , INTPAD中bit1等于1的话INTOFFSET值就等于1 , INTOFFSET : 用来显示INTPND中哪一位被设置为1

在这里插入图片描述

设置CPU,CPSR的I位,打开IRQ中断总开关。

搬砖文章:ARM架构的异常与中断
搬砖文章:【嵌入式开发】ARM 异常向量表 ( 异常概念 | 异常处理流程 | 异常向量 | 汇编代码 )
搬砖文章:ARM指令中特殊符号意义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值