IMX6ULL裸机学习(9)— IMX6ULL的异常和中断

IMX6ULL裸机学习(9)— IMX6ULL的异常和中断

一、什么是异常

CPU 在运行的过程中,会被各种“异常”打断,这些“异常”有:
① 指令未定义
② 指令、数据访问有问题
③ SWI(软中断)
④ 快中断
⑤ 中断

我们可以看到,中断是一种特殊的异常

二、IMX6ULL的异常处理机制

对于CPU来说,它每执行完一条指令都会检查有无异常产生,当CPU发现有异常产生时,它就会进行处理。首先,会把当前PC寄存器的值保存到LR(R14)寄存器中,然后对于不同的异常,跳去不同的地址执行程序。这些地址由VBAR寄存器来设置的,VBAR寄存器里存放着一个基地址,然后各种异常的地址在这个基地址后按顺序排列。我们可以在这些地址按照各种异常存放一条跳转指令,然后跳去执行某个函数(地址),这个就是异常向量,这些地址就是异常向量表。

对于Corext-A7来说,基本上的异常机制都是类似的
我们打开U-Boot的官网:http://www.denx.de/wiki/U-Boot/,点击源码,找到其托管在gitlab上的源码:https://gitlab.denx.de/u-boot/u-boot
在这里插入图片描述
arch/arm/lib/目录下的vectors.S文件可以看到如下向量表

	b	reset								
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq
	ldr	pc, _fiq

然后我们就可以修改我们的start.s如下所示

.text                               /* .text段保存代码,是只读和可执行的,后面的指令都属于.text段。 */
.global  _start                     /* .global表示_start是一个全局符号,会在链接器链接时用到 */

_start:                             /* 标签_start,汇编程序的默认入口是_start */
	b	reset						/* 复位异常,复位后从这里开始执行 */		
	ldr	pc,=_undefined_instruction  /* 未定义的指令异常,当cpu执行到不认识的指令时,会跳转到这里 */
	ldr	pc,=_software_interrupt     /* 软中断异常,当cpu执行svc指令时,会跳转到这里  */
	ldr	pc,=_prefetch_abort         /* 预取中止异常,处理器预取指令的地址不存在,或不允许当前访问时 */
	ldr	pc,=_data_abort             /* 数据中止异常,处理器数据访问指令的地址不存在,或不允许当前访问时 */
	ldr	pc,=_not_used               /* 未使用,预留的地址 */
	ldr	pc,=_irq                    /* 中断请求有效,且CPSR中的I位(即中断屏蔽位)为0时,产生IRQ异常 */
	ldr	pc,=_fiq                    /* 快速中断请求有效,且CPSR中的F位(即快中断屏蔽位)为0时,产生FIQ异常 */
reset:
    /* 1、设置栈 */
    ldr sp, =(0x80000000+0x100000)  /* 设置栈顶地址 */

    /* 2、清除bss段 */
    ldr r1, =__bss_start            /* 将bss段开始地址存入r1寄存器 */
    ldr r2, =__bss_end              /* 将bss段结束地址存入r2寄存器 */
    b clean_bss
clean:
    mov r3, #0                      /* 将0存入r3寄存器 */
    str r3, [r1], #4                /* 将r3中的值存到r1中的值所指向的地址中, 同时r1中的值加4 */
clean_bss:
    cmp r1, r2                      /* 比较r1和r2内的值是否相等 */
    bne clean                       /* 如果不相等则跳转到clean标签处,如果相等则往下执行 */                      

    /* 3、跳转到led函数 */
    bl main

    /* 4、原地循环 */
    b .

三、异常分类

1、复位

所有处理器核都有复位输入,并且在复位后将立即执行复位异常。 它是最高优先级的异常,无法屏蔽。上电后,此异常用于在处理器核上执行代码以对其进行初始化。

2、未定义的指令异常

简单地说,就是 CPU 或协处理器不认识这条指令,执行这样的指令时就会产生“未定义指令异常”。

如果 CPU核尝试使用操作码执行一条指令(在 ARM 体系结构规范中描述为 UNDEFINED),或者执行了协处理器指令但没有协处理器将其识别为可以执行的指令,则会导致未定义的指令异常。 在某些系统中,代码可能包含用于协处理器(例如 VFP协处理器)的指令,但是系统中不存在相应的 VFP 硬件。另外, VFP 硬件有可能无法处理特定指令,而是想调用软件来对其进行仿真。或者,可能会禁用 VFP 硬件, 采用异常处理,以便可以启用它,然后重新执行指令。
使用未定义的指令,可以实现一些仿真器。比如在你的芯片中,它并未支持某条硬件除法指令,但是你 还可以在代码中使用它。当 CPU执行这条指令时会发生异常,在异常处理函数中,你用软件来实现该指令的 功能。
对于不是特别设置的未定义指令,在异常处理函数中不能处理它时,通常做法是记录适当的调试信息, 并杀死对应的应用程序。
在某些情况下,未定义指令异常的另一个用途是实现用户断点:调试器去修改代码,替换断点位置的指 令为一条未定义指令。

3、SVC 异常

简单地说就是执行 SVC 这条汇编指令时就会触发这个异常, CPU 就会跳转过来执行 SVC 异常向量的代码。

supervisor call( SVC)通常在用户模式下使用,这使得用户模式的代码能够访问 OS 功能。例如,如果用户代码想要访问系统的特权部分(例如执行文件 I / O),则通常将使用 SVC 指令执行此操作。 在 Linux中对文件的 open/read/write 等 APP 层的系统函数,它的本质都是执行 SVC 指令,从而进入 Linux 内核中预设的 SVC 异常处理函数,在内核里操作文件。
注意: ARM9 等比较老的芯片里,这个异常是 SWI 异常,对应的指令是 SWI。

4、中止异常

中止异常可以在指令预取失败(预取中止)或数据访问失败(数据中止)时生成。它们可以来自外部存储器系统,在存储器访问时给出错误响应(可能表明指定的地址不对应于系统中的实际存储器)。另外,中止可以由内核的内存管理单元( MMU)生成。操作系统可以使用 MMU 中止来为应用程序动态分配内存。

预取一条指令时,可以在指令流水线中将其标记为已中止。仅当内核尝试执行它时,才导致预取中止异常。异常发生在指令执行之前。如果标记为中止的指令到达指令流水线的执行阶段之前刷新了指令流水线,则不会发生中止异常。数据中止异常发生在加载或存储指令执行时,并且是在尝试读取或写入数据之后发生的。

“中止异常”的处理程序代码在系统之间可能有很大差异。在许多嵌入式系统中,中止异常表示意外错误,处理程序将记录所有诊断信息,报告错误并让应用程序(或系统)退出。在使用 MMU 支持虚拟内存的系统中,中止处理程序可以将所需的虚拟页加载到物理内存中。 比如一个程序很大,没必要在一开始执行时就把它全部读入内存。等程序执行时发现内存中没数据时,就会触发中止异常,在异常的处理函数中再读入更多的数据。

5、中断

ARMv7-A 内核提供两种中断,称为 IRQ 和 FIQ。 FIQ 的优先级高于 IRQ,并且 FIQ 模式下可用的更多的备份寄存器,因此FIQ 具有一些潜在的速度优势。高优先级保证了它能尽快被处理,有更多备份寄存器可以节省将寄存器保存到堆栈时耗费的时钟周期。
FIQ、 IRQ 异常通常都与处理器核上的输入引脚相关联: 外部硬件会触发一条中断请求线, 通过中断控制器去中断处理器。 FIQ 和 IRQ 都是发给处理器核的物理信号, 如果处理器的 CPSR 寄存器中 FIQ 和 IRQ 处于打开状态,它将处理相应的异常。 几乎在所有系统上,通过使用中断控制器连接各种中断源。中断控制器对中断进行仲裁并确定优先级,然后依次提供串行化的单个信号,然后将其连接到内处理器核的 FIQ 或 IRQ 引脚。

系统中如果有某个中断的处理需要有严格的时间保证,你可以把它设置为 FIQ。对 FIQ 的设计是精心考虑的,它是向量表中的最后一项。一般的异常向量里存跳转指令,但是 FIQ 向量位于最后一项,处理程序可以直接放置在向量入口位置,并从该地址开始顺序运行。这避免了分支指令和任何相关的延迟,从而加快了 FIQ 响应时间。相对于其他模式, FIQ 模式下可用的备份寄存器数量比较多,从而避免要将寄存器的值保存到栈上,提高了执行速度。

四、异常处理过程

发生异常时, ARM 内核会自动执行以下操作:

1.将 CPSR 复制到 SPSR_mode, 比如发生 IRQ 异常时,当前 CPSR 就会被保存到 SPSR_IRQ 里。
2.将返回地址存储在新模式的链接寄存器( LR)中。
3.将 CPSR 模式位修改为与异常类型相关联的模式。
4.将 PC 设置为指向异常向量表中的相关指令。

对于cortex A7来说,如上这些就是硬件自动完成的,但这还不够,我们需要通过软件来做的是还是比较多的,如下所示,

1、保存现场
2、分辨异常/中断
3、调用对应的处理函数
4、恢复现场

但是对于cortex M3/M4来说,保存/恢复现场是由硬件实现。

五、异常相关汇编

对于异常向量表基地址的设置有两个指令,如下所示,其中MRC指令用来将VBAR的值读取到Rt寄存器中,MCR指令用来将Rt寄存器里的值写入VBAR中

MRC p15, 0, < Rt >, c12, c0, 0
MCR p15, 0, < Rt >, c12, c0, 0

所以我们可以在start.s中添加如下代码这只向量表基地址

    /* 2、设置异常向量表基地址 : VBAR */
	ldr r0, =_start
	mcr p15, 0, r0, c12, c0, 0

然后就是从异常处理程序返回时,要从异常处理程序返回,必须进行两个单独的操作:
1.从保存的 SPSR 中恢复 CPSR。
2.将返回地址写入 PC 寄存器。

在 ARM 体系结构中,这可以通过使用两类指令从异常中返回:
① RFE 指令: 它将链接寄存器和 SPSR 从当前模式堆栈弹出。
② 其他会设置 PC 寄存器指令,并让该指令带有 S 后缀: 例如“ SUBS PC, LR, #offset” , 注意:“ S”表示从 SPSR 中恢复 CPSR。

如果异常处理程序入口,使用堆栈来保存现场,则它可以使用带有“ ^” 限定符的加载指令返回。例如,异常处理程序可以使用以下命令在一条指令中返回:

LDMFD sp!, {
    
    pc} ^
LDMFD sp!, {
    
    R0-R12, pc} ^

在此示例中, ^限定符表示 SPSR 同时复制到 CPSR。

六、附录

上一篇:IMX6ULL裸机学习(8)— 清除BSS段
下一篇:IMX6ULL裸机学习(10)— 未定义异常和SVC异常实例

猜你喜欢

转载自blog.csdn.net/qq_38113006/article/details/112442897