文章目录
零 前言
本文只供参考按键中断程序示例,arm裸机编程始终不是最终的目标,能够完成简单的外设驱动,能理解裸机编程流程即可,要不求甚解,最后的目标还是系统移植,还是在linux下编程,所以不要对学不会裸机编程过于烦躁。
异常的相关概念和响应,到底哪个放在前面好理解呢?我认为先知道异常的相应原理再去看概念容易被接受,但是不知道有哪些概念,又无法知道响应时到底在干嘛。所以本文还是按先概念再相应原理的顺序来安排,但是本文读者应该是有基础的开发者,因此可以自由翻阅,或者先大致浏览概念,仔细看过异常相应之后再来详细看概念。
一 ARM的异常与中断
概述:当异常发生时,处理器会把pc设置为一个特定的地址,即让程序跳转到那个地址去执行那里的指令;这个地址位于中断向量表中,表中是固定好顺序的几个跳转指令;
存储器映射地址0x0是为向量表保留的,很多处理器的向量表都是从0x0地址开始;在某些操作系统如linux中,向量表地址可以选择到其他地址开始;
1 异常有哪些
1.1 七种异常的信息收集表
arm的异常总共有7种。
异常类型 | 英文名 | 处理器模式 | 优先级 | 返回地址 | 返回地址的用途 | 执行地址 |
---|---|---|---|---|---|---|
复位异常 | reset | svc特权模式 | 1 | - | 复位没有lr | 0x0 |
未定义指令异常 | undefined interrupt | undefined未定义指令中止模式 | 7 | lr | 指向未定义指令的下一条指令 | 0x04 |
软中断异常 | swi | svc特权模式 | 6 | lr | 指向swi指令的下一条指令 | 0x08 |
预取异常 | prefetch abort | abort指令预取中止模式 | 5 | lr-4 | 指向导致预取指令异常的那条指令 | 0x0c |
数据异常 | data abort | abort数据访问中止模式 | 2 | lr-8 | 指向导致数据中止异常的指令 | 0x10 |
外部中断异常 | irq | irq外部中断请求模式 | 4 | lr-4 | 指向发生异常时正在执行的指令 | 0x18 |
快速中断异常 | fiq | fiq快速中断请求模式 | 3 | lr-4 | 指向发生异常时正在执行的指令 | 0x1c |
注:0x14地址未使用;
1.2 七种异常的产生
1.2.1 复位异常
通常用于系统上电和软硬件导致系统复位两种情况,产生复位异常中断。
复位即从头开始,复位异常中断处理程序进行的是初始化工作。下面列出一些常见的工作:
- 初始化数据栈和寄存器;
- 初始化存储系统,如系统的MMU;
- 初始化关键的I/O设备;
- 使能中断;
- 将处理器切换到合适的模式;
- 初始化C变量,跳转到应用程序执行;
1.2.2 未定义指令异常
见名知意,就是arm遇到了一条未定义的指令,这种情况不是应该报错吗?为啥还有专门的异常。
因为有协处理的存在,arm中有专门的协处理器指令来操作它,如果程序中写了协处理器指令,但是外部的协处理器未响应,或者根本没有协处理器时,无法执行,则发生未定义指令异常;也就是说,这是一种与硬件相关的异常,而不是一种语法错误,与编译器无关;
我们可以利用它的这种特点,来实现指令集的扩展:
协处理器有很多种,选择当前系统中没有的协处理器,运行其相关的指令,使之可以发生未定义指令异常,在异常处理程序中放上自己想要的代码,可以仿真这种协处理器的功能,也可以干别的事。
如果要仿真,步骤如下:
(1) 将仿真程序入口地址链接到向量表中0x4处。(在start.s中)
(2) 读取该未定义指令的bits[27:24]位,判断其是否是一条协处理器指令,0b1110或者0b110x是协处理器指令,然后根据[11:8]位判断具体是哪一条指令,然后实现其功能;
不仿真的话,就在中断处理程序中写自己的代码;
1.2.3 软中断异常
软中断由swi指令产生,即由程序中的代码产生,与硬件电路无关;软中断进入的是svc模式;
1.2.4 预取异常
由系统存储器报告,当处理器去取一条被标记为预取无效的指令时,发生预取异常。
如果系统中不包含MMU,指令预取异常中断处理程序只是简单报告错误并退出,否则引起异常的指令的物理地址被存储到内存中。
1.2.5 数据异常
数据异常:当存储器访问指令load/store执行,但目标地址不存在或者该地址不允许访问时,由存储器发出数据终止信号。
1.2.6 外部中断异常
当处理器的外部中断请求引脚有效,而且CPSR的I位被清0,产生外部中断异常。
1.2.7 快速中断异常
当快速中断请求引脚有效且CPSR寄存器的F位被清0,发生快速中断异常。
2 异常的优先级
优先级高的优先执行;
3 异常与处理器模式
每种异常都会进入一种特定的模式。
用户模式和系统模式不可通过异常进入,只能通过编程来切换。也就是说,在中断处理程序的最后要切换回用户模式;
4 异常的响应与返回
4.1编程时需要知道的:
4.1.1 异常发生时
4.1.1.1 系统做了什么?
(1) 用spsr保存cpsr;
(2) 改CPSR寄存器:
- 改工作模式;
- 让处理器模式为:arm;
- 禁止中断;(如果需要)
(3) 保存返回地址到lr;
(4) 设置pc为相应的中断向量;
4.1.1.2 程序需要做什么?
保存现场到栈区:
用连续地址访问指令ldrm把用到的寄存器和lr一次性保存到栈区我们自己申请的空间内;
4.1.2 当中断程序执行完之后, 程序需要做什么?
(1) 恢复现场–》恢复寄存器的值:用连续地址访问指令strm把栈区我们自己申请的空间内的数据放回到异常之前的寄存器和lr中;
(2) 将spsr的值恢复到cpsr–》恢复工作模式,工作模式不会自动回复,需要我们手动恢复;
(3) 恢复lr到pc—》继续执行之前的代码;那不如直接把栈中的数据返回到pc中,不要中间经过lr;
4.2 来看详细的
4.2.1 arm响应流程
1.判断处理器状态
上述4.1.1.1中的切换处理器状态,异常发生时,处理器自动切换到arm状态,所以在异常处理函数中要判断,在异常发生前,是arm还是thumb状态,通过spsr的T位可以判断。
一般只在swi处理函数中才需要知道异常发生前处理器的状态,所以在thumb状态下,调用swi软中断异常需要注意:发生异常的指令地址是lr-2而不是lr-4;thumb是16位指令,用LDRH指令来判断中断向量号;
2.向量表
每个异常发生时,程序都会从异常向量表中开始跳转,一般向量表中放的就是跳转指令;
其中FIQ向量地址是0x1c,也就是最后一个向量,那么,FIQ_Handler()可以直接从0x1c开始,省下一条跳转指令;
4.2.2 关于返回地址
1.1表中列出了每种异常在返回时需要跳转的地址;
解释:
根据三级流水线,假设有连续的三条指令a; b; c; d; 当处理器将要执行a指令时,pc已经在c指令的地址即pc+8,所以pc-8是a的地址,pc-4是b的地址;
当异常发生时,处理器会把正在执行的指令的下一条指令的地址保存在lr中,也就是pc-4;所有异常都是如此,但是根据不同异常的发生方式,在返回时,还需要对lr上的地址做一点处理;
(1) swi和未定义指令异常,在执行指令时处理异常,返回地址就是lr;
(2) IRQ和FIQ,处理器会把当前指令执行完再处理异常,处理中断时pc已经指到了d指令,所以保存在lr中的pc-4指的是c指令;而我们要返回到b指令,在返回时,需要再补-4才能回到b指令,所以返回地址是lr-4;
(3) 指令预取中止异常,比如b指令是一个无效指令,在预取b指令的时候,b会被标记为预取无效,同时b之前的指令仍然正常运行,当执行到b,pc指d,预取中止异常发生,pc-4也就是c的地址被写入lr,好像跟(1)中一样,但是预取中止异常时程序要返回到b指令那里取读b指令,要返回到b处,对lr补-4;所以返回地址是lr-4;
(4) 数据中止异常,实在访问数据出错时候产生的,也就是在数据访问指令执行结束后才发生异常,而所以返回地址应该为lr-4,但中止异常返回时,需要返回到导致异常的指令,因此再补-4;所以最后返回地址是lr-8;例如执行a访问数据,pc指c,访问出错发生异常是在a执行后,pc指d,而异常返回要求返回到a,所以lr-4指向b,所以要用lr-8返回a;
(5) 复位异常不需要返回地址;