韦东山嵌入式Linux学习----011 异常与中断

异常与中断

/*
 *硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
 *软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
 *参考资料:https://wenku.baidu.com/view/6e2a774a336c1eb91a375d64.html,
  S3C2440datasheet,开发版原理图,《ARM体系与编程》杜春雷
*/

一、基础知识点

  1. 特权模式与用户模式
    多数系统将处理器工作模式划分为特权模式用户模式
    1.1 特权模式:一般指操作系统管理程序运行的状态,具有较高的特权级别,又称为特权态(特态)、系统态、管态。当处理器处于管态时全部指令(包括特权指令)可以执行,可使用所有资源,并具有改变处理器状态的能力。
    1.2 用户模式:一般指用户程序运行时的状态,具有较低的特权级别,又称为普通态(普态)、用户态、目态。当处理器处于目态时,就只有非特权指令能执行

  2. 使用到的寄存器简称

           程序计数器(PC)
           堆栈指针寄存器(SP)
           链接寄存器(LR)
           程序状态寄存器(CPSR)
           程序状态保存寄存器(SPSR)
       }

  3. 代码密度
    代码密度:单位存储空间中包括的指令个数。比如ARM指令是32位的。而Thumb指令时16位的,假设在1K的存储空间中,能够放32条ARM指令,就能够放64条Thumb指令,因此在存放Thunb指令时,代码密度高

二、什么是异常与中断

  1. 异常:是指CPU内部出现的中断,即在CPU执行特定指令时出现的非法情况。同时异常也称为同步中断
    因此只有在一条指令执行后才会发出中断 ,不可能在指令执行期间发生异常。
  2. 中断:也称为异步中断。因此它是由其他硬件设备依照 CPU 时钟信号随机产生,即意味着中断能在指令之间发生
    中断又分为外部可屏蔽中断(INTR)外部非屏蔽中断(NMI),所用I0设备产生的中断请求均引起可屏蔽中断。

三、ARM怎样处理中断/异常

  1. 初始化a.设置中断源,让其可以产生中断
                  b.设置中断控制器(屏蔽优先级)
                  c.设置CPU总开关
  2. 执行程序:(开发者自行设计的程序)
  3. 产生中断:产生中断:某个动作信号——>中断控制器——>CPU
    CPU每执行完一条指令,都会检查有无中断或异常产生
    发现有异常/中断产生,开始处理

           对于不同异常跳去不同地址执行程序
      }
  4. 异常/中断程序处理
    {
           a.保存现场(可保存在各种寄存器中)
           b.处理异常/中断
                 {
                         分辨中断源
                         在调用不同的函数
                 }
           c.恢复现场
       }

提问:中断程序怎么被调用?
回答:主要通过硬件和软件的协调作用
—硬件:cpu通过跳转指令到达某个地址(查询可知地址0x18)——>0x18
—软件:0x18中保存着处理函数——>保存现场——>处理异常/中断(分辨中断源、调用对应函数)——>恢复现场

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

  1. 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声明。

  2. 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——状态位
  1. 硬件处理异常流程
    ----在进入异常时:
           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的值相符合。

发布了6 篇原创文章 · 获赞 0 · 访问量 214

猜你喜欢

转载自blog.csdn.net/weixin_42813232/article/details/104350016