异常与中断
/*
*硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
*软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
*参考资料:https://wenku.baidu.com/view/6e2a774a336c1eb91a375d64.html,
S3C2440datasheet,开发版原理图,《ARM体系与编程》杜春雷
*/
一、基础知识点
-
特权模式与用户模式
多数系统将处理器工作模式划分为特权模式和用户模式
1.1 特权模式:一般指操作系统管理程序运行的状态,具有较高的特权级别,又称为特权态(特态)、系统态、管态。当处理器处于管态时全部指令(包括特权指令)可以执行,可使用所有资源,并具有改变处理器状态的能力。
1.2 用户模式:一般指用户程序运行时的状态,具有较低的特权级别,又称为普通态(普态)、用户态、目态。当处理器处于目态时,就只有非特权指令能执行。 -
使用到的寄存器简称
{
程序计数器(PC)
堆栈指针寄存器(SP)
链接寄存器(LR)
程序状态寄存器(CPSR)
程序状态保存寄存器(SPSR)
} -
代码密度
代码密度:单位存储空间中包括的指令的个数。比如ARM指令是32位的。而Thumb指令时16位的,假设在1K的存储空间中,能够放32条ARM指令,就能够放64条Thumb指令,因此在存放Thunb指令时,代码密度高。
二、什么是异常与中断
- 异常:是指CPU内部出现的中断,即在CPU执行特定指令时出现的非法情况。同时异常也称为同步中断,
因此只有在一条指令执行后才会发出中断 ,不可能在指令执行期间发生异常。 - 中断:也称为异步中断。因此它是由其他硬件设备依照 CPU 时钟信号随机产生,即意味着中断能在指令之间发生。
中断又分为外部可屏蔽中断(INTR)和外部非屏蔽中断(NMI),所用I0设备产生的中断请求均引起可屏蔽中断。
三、ARM怎样处理中断/异常
- 初始化:a.设置中断源,让其可以产生中断
b.设置中断控制器(屏蔽优先级)
c.设置CPU总开关 - 执行程序:(开发者自行设计的程序)
- 产生中断:产生中断:某个动作信号——>中断控制器——>CPU
CPU每执行完一条指令,都会检查有无中断或异常产生
发现有异常/中断产生,开始处理
{
对于不同异常跳去不同地址执行程序
} - 异常/中断程序处理
{
a.保存现场(可保存在各种寄存器中)
b.处理异常/中断
{
分辨中断源
在调用不同的函数
}
c.恢复现场
}
提问:中断程序怎么被调用?
回答:主要通过硬件和软件的协调作用
—硬件:cpu通过跳转指令到达某个地址(查询可知地址0x18)——>0x18
—软件:0x18中保存着处理函数——>保存现场——>处理异常/中断(分辨中断源、调用对应函数)——>恢复现场
四、CPU模式(Mode)_状态(State)与寄存器
-
CPU的State:
{
ARM State:AMR指令集,每个指令4byte
THUMB State:THUMB指令集,每个指令2byte(多用于单片机,单片机Flash空间小)
}
二者的区别:
Thumb指令能够看做是ARM指令压缩形式的子集。是针对代码密度的问题而提出的。它具有16位的代码密度。Thumb不是一个完整的体系结构,不能指望处理程序仅仅运行Thumb指令而不运行ARM指令集。因此。Thumb指令仅仅须要支持通用功能。必要时,可借助完好的ARM指令集,比如:全部异常自己主动进入ARM状态。在编写Thumb指令时。先要使用伪指令CODE16声明,并且在ARM指令中要使用BX指令跳转到Thumb指令,以切换处理器状态。编写ARM指令时,可使用伪指令CODE32声明。 -
CPU分为七种模式:
{
用户模式:usr
特权模式:
{
系统模式:sys
异常模式:
{
未定义指令模式:Undefined(und)
管理模式:Supervisor(svc)
中止模式:Abort(abt)
中断模式:IRQ(irq)
快中断模式:FIQ(fiq)
}
}
}
!特权模式可以编程操作CPSR任意切换六种模式,用户模式下则不可!
提问:如何在用户模式下切换其他模式?
回答:实现模式切换,可通过软件中断的方式实现异常,如中断指令SWI(具体操作细节在下面第四个swi异常模式有讲)。ARM State下七种模式的使用的寄存器如下图:
分析:每个模式需要用到的寄存器有相同的,也有每个模式下专用的寄存器 在异常模式下,R13为SP寄存器,R14位LR寄存器(用于保存发生异常时的指令地址)
THUMB State下七种模式的使用的寄存器如下图
分析:每个模式需要用到的寄存器有相同的,也有每个模式下专用的寄存器. 有8个状态寄存器集通用寄存器,R0-R7,以及程序计数器(PC),堆栈指针寄存器(SP),链接寄存器(LR),、程序状态寄存器(CPSR)和程序状态保存寄存(SPSR) !SPSR用来保存“被中断模式的CPSP”!
程序状态寄存器CPSR,SPSR分析
M0-M4——Mode bits,设置模式的切换(如果处于用户模式则无法修改),具体如上图
T——State bits,切换状态,T=1,为THUMB状态,T=0,为ARM状态
F——FIQ disable,F=1,FIQ中断被禁止
I——IRQ disable,I=1,IRQ中断被禁止
bit8-bit27——Resverved保留位
bit28-bit31——状态位
- 硬件处理异常流程
----在进入异常时:
a.在适当的链接寄存器中保留下一条指令的地址。如果有例外的话从ARM状态输入,然后将下一条指令的地址复制到链接寄存器(即,当前PC + 4或PC + 8取决于异常。详见表2-2)。如果有例外的话从THUMB状态输入,然后写入链接寄存器的值是当前PC偏移量的一个值使程序在从异常返回时从正确的位置恢复。这意味着异常处理程序不需要确定异常是从哪个状态输入的。
公式表达:LR-异常 = 下一条指令地址(被中断指令)即PC+4/PC+8
b.将CPSR的数据传输到适当的SPSR中。
公式表达:SPSR—异常 = CPSR
c.将CPSR模式位(M0-M4——Mode)修改,进入异常模式
d.跳到向量表
---- 退出异常时
a.将链接寄存器移到PC上,在适当的地方减去偏移量
公式表达:PC = LR-异常 - offset
b.将SPSR的数据传输回CPSR
公式表达:CPSR = SPSR-异常
c.清除中断禁用标志(如果发生了中断)
五、und模式
5.1 汇编指令知识点
stmdb:
举例:stmdb sp!,{r0-r12,lr}
含义:sp = sp - 4,先压lr,sp = lr(即将lr中的内容放入sp所指的内存地址)。
sp = sp - 4,再压r12,sp = r12。sp = sp - 4,再压r11,sp = r11......sp = sp - 4,最后压r0,sp = r0。
ldmia:
如果想要将r0-r12和lr弹出,可以用ldmia指令。
举例:ldmia sp!, {r0-r12,pc}^
mrs:状态寄存器传送至通用寄存器类指令
功能:将状态寄存器的内容传送至通用寄存器。
格式:MRS{<条件码>}Rd,CPSR}SPSR
其中:
Rd 目标寄存器,Rd不允许R15。
条件码=0 将CPSR中的内容传送目的寄存器。
条件码=1 将SPSR中的内容传送至目的寄存器。
bic:位清除指令
指令格式:BIC{cond}{S} Rd,Rn,operand2
BIC指令将Rn 的值与操作数operand2 的反码按位逻辑”与”,结果存放到目的寄存器Rd 中。
指令示例:BIC R0,R0,#0x0F ;将R0最低4位清零,其余位不变。
mov:数据传送指令
用于将一个数据从源地址传送到目标地址。!其特点是不破坏源地址单元的内容!。
5.2 代码编写流程
5.2.1思路说明:
通过修改start.S文件,添加未定义指令,使开发板在运行时进入und模式,通过串口通信的方式打印出CPSR中的的信息,根据其bit0—bit4的数字,判断是否进入und模式。
5.2.2 start.S文件源码
.text
.global _start
_start:
b reset /* vector 0:reset */
b do_und /* vector 4:und_addr */
/*可以改成ldr pc, und_addr /* vector 4 : und */
*und_addr:
.word do_und
*目的:保证du_und程序可正确在sdram中运行
*/
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/*
* 1.保存现场
* 2.处理异常
* 3.恢复现场
*/
ldr sp, =0x34000000 //und模式下的sp寄存器为该模式下专用寄存器,使用前需要对其进行设置
/* 在进入und模式的时候,查表可知r0-r12寄存器的值可能会被修改,所以需要对其进行保存 */
stmdb sp!, {r0-r12,lr}
/* 处理异常函数:把cpsr内的信息放到r0,执行printException */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场,把spsr寄存器的值传输回cpsr*/
ldmia sp!, {r0-r12,pc}^
und_string:
.string "undefined instruction exception"
.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] /* 恢复原来的值 */
/*可以改为
ldr pc, =sdram
sdram:
bl uart0_init
*/
//这里的代码用来重定位
bl sdram_init
bl copy_to_sdram
bl clean_bss
bl uart0_init //假设进入了und模式,此时程序没有进入main,uart0_init无法定义
bl print1 //用于调试程序
/*故意定义一条未定义指令,使cpu进入und模式 */
und_code:
.word 0xdeadc0de //word定义的是一变量,会开辟占用内存
bl print2 //用于调试程序
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
5.2.3 Makefile文件源码
all: start.o led.o uart.o sdram_init.o main.o exception.o
arm-linux-ld -T sdram.lds $^ -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
%.o : %.c
arm-linux-gcc -c -o $@ $<
%.o : %.S
arm-linux-gcc -c -o $@ $<
!这里注意一下start.o要放前面,链接的时候按放的顺序来,start.o里面有关闭看门狗那些初始化,放后面链接出来就无法先初始化板子,会导致部分程序运行不了 !
5.2.4 新建一个exception.c文件
#include "uart.h"
void printException(unsigned int cpsr, char *str)//打印cpsr寄存器中的信息
{
puts("Exception! cpsr = ");
printHex(cpsr);
puts(" ");
puts(str);
puts("\n\r");
}
uart.c添加调试程序代码:
void print1(void)
{
puts("abc\n\r");
}
void print2(void)
{
puts("123\n\r");
}
5.2.5 最终结果
观察可知,程序可以正确运行,cpsr = 0x600000DB,bit0-bit4 = 11011查表可知,发生了异常模式,与代码预期想法一致。
六、swi模式
6.1 基础知识点
1、软件中断:用户可通过在程序里写入SWI指令来切换到管理模式,当CPU执行到SWI指令时会从用户模式切换到管理模式下,执行软件中断处理。
2、swi指令格式:SWI{cond} immed_24
Cond域:是可选的条件码 (参见 ARM汇编指令条件执行详解)
immed_24域:通过查手册可知,swi指令是24位立即数,范围从0到2^(24)-1的表达式,(即0-16777215)。
用户程序可以使用该常数来进入不同的处理流程。
6.2 代码编写流程
6.2.1 start.S文件源码
start.S可仿照und模式的代码进行编写
{
编写swi指令触发异常
swi 0x123
确定swi异常跳转地址,查手册可知为0x08 /pictuire8/
_start:
b reset /* vector 0:reset */
ldr pc, und_addr /* vector 4:und_addr */
ldr pc, swi_addr /* vector 8:swi_addr */
编写异常处理函数
do_swi:
.....
}
start.S文件完整代码:
.text
.global _start
_start:
b reset /* vector 0:reset */
ldr pc, und_addr /* vector 4:und_addr */
ldr pc, swi_addr /* vector 8:swi_addr */
und_addr:
.word do_und
swi_addr:
.word do_swi
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/*
* 1.保存现场
* 2.处理异常
* 3.恢复现场
*/
ldr sp, =0x34000000 //und模式下的sp寄存器为该模式下专用寄存器,使用前需要对其进行设置
/* 在进入und模式的时候,查表可知r0-r12寄存器的值可能会被修改,所以需要对其进行保存 */
stmdb sp!, {r0-r12,lr}
/* 处理异常函数:把cpsr内的信息放到r0,执行printException */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场,把spsr寄存器的值传输回cpsr*/
ldmia sp!, {r0-r12,pc}^
und_string:
.string "undefined instruction exception"
.align 4
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x8的地方执行程序
*/
/*
* 1.保存现场
* 2.处理异常
* 3.恢复现场
*/
ldr sp, =0x33e00000 //und模式下的sp寄存器为该模式下专用寄存器,使用前需要对其进行设置
/* 在进入und模式的时候,查表可知r0-r12寄存器的值可能会被修改,所以需要对其进行保存 */
stmdb sp!, {r0-r12,lr}
mov r4,lr //把lr寄存器下一条即将执行的指令的地址传到r0
/* 处理异常函数:把cpsr内的信息放到r0,执行printException */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
sub r0,r4,#4 //r4地址减四就是swi指令执行地址
bl printSWIVal
/* 恢复现场,把spsr寄存器的值传输回cpsr*/
ldmia sp!, {r0-r12,pc}^
swi_string:
.string "swi exception"
.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 copy_to_sdram
bl clean_bss
/*
*复位之后, cpu处于svc模式
*系统先进入用户模式,修改cpsr寄存器的值
*后在swi指令触发异常
*/
mrs r0,cpsr /* 读出cpsr的值 */
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
msr cpsr, r0 /* 写入值,改变cpsr的值*/
/* 设置usr模式下的sp寄存器 */
ldr sp, =0x33f00000
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/*故意定义一条未定义指令,使cpu进入und模式 */
und_code:
und_code:
.word 0xdeadc0de //word定义的是一变量,会开辟占用内存
bl print2
swi 0x123 /* swi指令,用来触发异常 */
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
6.2.2 uart.c文件添加代码:
void printSWIVal(unsigned int *pSWI)
{
puts("SWI val = ");
printHex(*pSWI & ~0xff000000); //SWI为24位立即数
puts("\n\r");
}
6.2.3 最终实验结果:
观察可知,程序可以正确运行,cpsr = 0x600000DB,bit4-bit0 = 11011查表可知,发生了未定义模式。后进入usr模式之后发生了swi指令触发的异常,,cpsr =0x600000D3,bit4-bit0 = 10011查表可知,进入了管理模式打印出来的SWIVal = 0x00000123,与swi 0x123的值相符合。