匿名
未登录
登录
百问网嵌入式Linux wiki
搜索
查看“第014课 异常与中断”的源代码
来自百问网嵌入式Linux wiki
名字空间
页面
讨论
更多
更多
页面选项
Read
查看源代码
历史
←
第014课 异常与中断
因为以下原因,您没有权限编辑本页:
您所请求的操作仅限于该用户组的用户使用:
用户
该页面已被保护以防止编辑和其他操作。
您可以查看与复制此页面的源代码。
=第001节_概念引入与处理流程= 取个场景解释中断 假设有个大房间里面有小房间,婴儿正在睡觉,他的妈妈在外面看书。 问:这个母亲怎么才能知道这个小孩醒? 1 过一会打开一次房门,看婴儿是否睡醒,让后接着看书 2 一直等到婴儿发出声音以后再过去查看,期间都在读书 第一种 叫做查询方式: 优点:简单 缺点: 累 写程序如何: <syntaxhighlight lang="c" > while(1) { 1 read book(读书) 2 open door(开门) if(睡) return(read book) else 照顾小孩 } </syntaxhighlight> 第二种叫中断方式: 优点:不累 缺点:复杂 写程序: <syntaxhighlight lang="c" > while(1) { read book 中断服务程序()//如何被调用? { 处理照顾小孩 } } </syntaxhighlight> 我们还是看看母亲被小孩哭声打断如何照顾小孩? 母亲的处理过程 1 平时看书 2 发生了各种声音,如何处理这些声音 有远处的猫叫(听而不闻,忽略) 门铃声有快递(开门收快递) 小孩哭声(打开房门,照顾小孩) 3 母亲的处理 只会处理门铃声和小孩哭声 a 现在书中放入书签,合上书(保存现场) b 去处理 (调用对应的中断服务程序) c 继续看书(恢复现场) 不同情况,不同处理 a 对于门铃:开门取快件 b 对于哭声:照顾小孩 我们将母亲的处理过程抽象化 母亲的头脑相当于CPU 耳朵听到声音会发送信号给脑袋,声音来源有很多种,有远处的猫叫,门铃声,小孩哭声。这些声音传入耳朵,再由耳朵传给大脑,除了这些可以中断母亲的看书,还有其他情况,比如身体不舒服,有只蜘蛛掉下来,对于特殊情况无法回避,必须立即处理 对比我们得arm系统 ---》图2 ![](需要替换://i.imgur.com/Yg9tRdq.png) 有CPU 有中断控制器,我们的中断源发送 中断控制器可以发信号给CPU告诉它发生了那些紧急情况 中断源有 按键 定时器 有其它的(比如网络数据) 这些信号都可以发送信号给中断控制器,再由中断控制器发送信号给CPU表明有这些中断产生了,这些成为中断(属于一种异常) 还有什么可以中断CPU运行 指令不对,数据访问有问题 reset信号,这些都可以中断CPU 这些成为异常中断 重点在于 保存现场以及恢复现场 处理过程 a 保存现场(各种寄存器) b 处理异常(中断属于一种异常) c 恢复现场 arm对异常(中断)处理过程 1 初始化: a 设置中断源,让它可以产生中断 b 设置中断控制器(可以屏蔽某个中断,优先级) c 设置CPU总开关,(使能中断) 2 执行其他程序:正常程序 3 产生中断:按下按键--->中断控制器--->CPU 4 cpu每执行完一条指令都会检查有无中断/异常产生 5 发现有中断/异常产生,开始处理 '''对于不同的异常,跳去不同的地址'''执行程序 这地址上,只是一条跳转指令,跳去执行某个函数 (地址) 指的是异常向量 如下就是异常向量表 对于不同的异常都有一条跳转指令 <syntaxhighlight lang="c" > .globl _start _start: 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 //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18** ldr pc, _fiq //我们先在0x18这里放 ldr pc ,__irq,于是cpu最终会跳去执行__irq代码 //保护现场,调用处理函数,恢复现场 </syntaxhighlight> 3-5都是硬件强制做的 6 这些函数做什么事情()软件做的 a 保存现场(各种寄存器) b 处理异常(中断): 分辨中断源 再调用不同的处理函数 c 恢复现场 对比母亲的处理过程来比较arm中断的处理过程 中断处理程序怎么被调用? CPU--->0x18 --跳转到其他函数-> 做保护现场 调用函数 分辨中断源 调用对应函数 恢复现场 cpu到0x18是由硬件决定的,跳去执行更加复杂函数(由软件决定) =第002节_CPU模式(Mode)_状态(State)与寄存器= 这节课我们来讲CPU的工作模式(Mode) 状态(State)寄存器 7种Mode: usr/sys undefined(und) Supervisor(svc) Abort(abt) IRQ(irq) FIQ(fiq) 2种State: ARM state Thumb state 寄存器: 通用寄存器 备份寄存器(banked register) 当前程序状态寄存器(Current Program Status Register);CPSR CPSR的备份寄存器:SPSR(Save Program Status Register) 我们仍然以这个母亲为例讲解这个CPU模式 这个母亲无压力看书 -->(正常模式) 要考试,看书--->(兴奋模式) 生病---->(异常模式) 可以参考书籍 《ARM体系结构与编程》作者:杜春雷 对于ARM CPU有7种模式 1 usr :类比 正常模式 2 sys :类比的话兴奋模式 3 5种异常模式:(2440用户手册72页) 3.1 und :未定义模式 3.2 svc :管理模式 3.3 abt :终止模式 a 指令预取终止(读写某条错误的指令导致终止运行) b 数据访问终止 (读写某个地址,这个过程出错) 都会进入终止模式 3.4 IRQ: 中断模式 3.5 FIQ: 快中断模式 我们可以称以下6种为特权模式 und :未定义模式 svc :管理模式 abt :终止模式 IRQ :中断模式 FIQ :快中断模式 sys :系统模式 usr用户模式(不可直接进入其他模式) 可以编程操作CPSR直接进入其他模式 ![](需要替换://i.imgur.com/cgghj9L.png) 这个图是有关各个模式下能访问寄存器的,再讲这个图之前我们先引入 2种state CPU有两种state 1 ARM state 使用ARM指令集,每个指令4byte 2 Thumb state 使用的是Thumb指令集,每个指令2byte 比如同样是 mov R0, R1 编译后 对于ARM指令集要占据4个字节:机器码 对于Thumb指令集占据2个字节:机器码 引入Thumb减少存储空间 百度搜索ARM指令集和Thumb指令的区别 现在先区分下ARM指令集与Thumb指令集 Thumb 指令可以看作是 ARM 指令压缩形式的子集,是针对代码密度的问题而提出的,它具有 16 位的代码密度但是它不如ARM指令的效率高 .Thumb 不是一个完整的体系结构,不能指望处理只执行Thumb 指令而不支持 ARM 指令集.因此,Thumb 指令只需要支持通用功能,必要时可以借助于完善的 ARM 指令集,比如,所有异常自动进入 ARM 状态.在编写 Thumb 指令时,先要使用伪指令 CODE16 声明,而且在 ARM 指令中要使用 BX指令跳转到 Thumb 指令,以切换处理器状态.编写 ARM 指令时,则可使用伪指令 CODE32声明. 下节课会演示使用Thumb指令集编译,看是否生成的bin文件会变小很多 ![](需要替换://i.imgur.com/cgghj9L.png) 在每种模式下都有R0 ~ R15 在这张图注意到有些寄存器画有灰色的三角形,表示访问该模式下访问的专属寄存器 比如 mov R0, R8 mov R0, R8 在System 模式下访问的是R0 ~ R8,在所有模式下访问R0都是同一个寄存器 mov R0,R8_fiq 但是在FIQ模式下,访问R8是访问的FIQ模式专属的R8寄存器,不是同一个物理上的寄存器 在这五种异常模式中每个模式都有自己专属的R13 R14寄存器,R13用作SP(栈) R14用作LR(返回地址) LR是用来保存发生异常时的指令地址 为什么快中断(FIQ)有那么多专属寄存器,这些寄存器称为备份寄存器 回顾一下中断的处理过程 1 保存现场(保存被中断模式的寄存器) 就比如说我们的程序正在系统模式/用户模式下运行,当你发生中断时,需要把R0 ~ R14这些寄存器全部保存下来,让后处理异常,最后恢复这些寄存器 但如果是快中断,那么我就不需要保存 系统/用户模式下的R8 ~ R12这几个寄存器,在FIQ模式下有自己专属的R8 ~ R12寄存器,省略保存寄存器的时间,加快处理速度 但是在Linux中并不会使用FIQ模式 2 处理 3 恢复现场 CRSR当前程序状态寄存器,这是一个特别重要的寄存器 SPSR保存的程序状态寄存器,他们格式如下 ![](需要替换://i.imgur.com/P5Tkmq5.png) 首先 M4 ~ M0 表示当前CPU处于哪一种模式(Mode) 我们可以读取这5位来判断CPU处于哪一种模式,也可以修改这一种模式位,让其修改这种模式 假如你当前处于用户模式下,是没有权限修改这些位的 M4 ~ M0对应什么值,会有说明 ![](需要替换://i.imgur.com/YShQWjP.png) 查看其他位 Bit5 State bits表示CPU工作与Thumb State还是ARM State用的指令集是什么 Bit6 FIQ disable当bit6等于1时,FIQ是不工作的 Bit7 IRQ disable当bit5等于1时,禁止所有的IRQ中断,这个位是IRQ的总开关 Bit8 ~ Bit27是保留位 Bite28 ~ Bit31是状态位, 什么是状态位,比如说执行一条指令 cmp R0, R1 如果R0 等于 R1 那么zero位等于1,这条指令影响 Z 位,如果R0 == R1,则Z = 1 beq跳转到xxx这条指令会判断Bit30是否为1,是1的话则跳转,不是1的话则不会跳转 使用 Z 位,如果 Z 位等于1 则跳转,这些指令是借助状态位实现的 SPSR保存的程序状态寄存器 表示发生异常时这个寄存器会用来保存被中断的模式下他的CPSR 就比如我我的程序在系统模式下运行 CPSR是某个值,当发生中断时会进入irq模式,这个CPSR_irq就保存系统模式下的CPSR 我们来看看发生异常时CPU是如何协同工作的 进入异常的处理流程(硬件) ![](需要替换://i.imgur.com/rI6ZYwF.png) 我们来翻译一下: 发生异常时,我们的CPU会做什么事情 1把下一条指令的地址保存在LR寄存器里(某种异常模式的LR等于被中断的下一条指令的地址) 它有可能是PC + 4有可能是PC + 8,到底是那种取决于不同的情况 2 把CPSR保存在SPSR里面(某一种异常模式下SPSR里面的值等于CPSR) 3 修改CPSR的模式为进入异常模式(修改CPSR的M4 ~ M0进入异常模式) 4 跳到向量表 退出异常怎么做? ![](需要替换://i.imgur.com/pJzcOIV.png) 1 让LR减去某个值,让后赋值给PC(PC = 某个异常LR寄存器减去 offset) 减去什么值呢? 也就是我们怎么返回去继续执行原来的程序,根据下面这个表来取值 ![](需要替换://i.imgur.com/9lPkInF.png) 如果发生的是SWI可以把 R14_svc复制给PC 如果发生的是IRQ可以把R14_irq的值减去4赋值给PC 2 把CPSR的值恢复(CPSR 值等于 某一个一场模式下的SPSR) 3 清中断(如果是中断的话,对于其他异常不用设置) =第003节_不重要_Thumb指令集程序示例= 在上节视频里说ARMCPU有两种状态 ARM State 每条指令会占据4byte Thumb State 每条指令占据2byte 我们说过Thumb指令集并不重要,本节演示把一个程序使用Thumb指令集来编译它 使用上一章节的重定位代码 打开Makefile和Start.S Makefile文件 <syntaxhighlight lang="c" > all: arm-linux-gcc -c -o led.o led.c arm-linux-gcc -c -o uart.o uart.c arm-linux-gcc -c -o init.o init.c arm-linux-gcc -c -o main.o main.c arm-linux-gcc -c -o start.o start.S #arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -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 </syntaxhighlight> 对于使用Thumb指令集 <syntaxhighlight lang="c" > all: arm-linux-gcc -mthumb -c -o led.o led.c//只需要在arm-linux-gcc加上 mthumb命令即可 arm-linux-gcc -c -o uart.o uart.c arm-linux-gcc -c -o init.o init.c arm-linux-gcc -c -o main.o main.c arm-linux-gcc -c -o start.o start.S #arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -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 </syntaxhighlight> 改进 <syntaxhighlight lang="c" > all: led.o uart.o init.o main.o start.o //all依赖led.o uart.o init.o main.o start.o #arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -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 -mthumb -c -o $@ $< //对于所有的.c文件使用规则就可以使用thumb指令集编译 $@表示目标 $<表示第一个依赖 %.o : %.S arm-linux-gcc -c -o $@ $< </syntaxhighlight> 对start.S需要修改代码 原重定位章节Start.S文件 <syntaxhighlight lang="c" > .text .global _start _start: /* 关闭看门狗 */ 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 //bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ ldr pc, =main /* 绝对跳转, 跳到SDRAM */ halt: b halt </syntaxhighlight> 使用thumb指令集的Start.S文件 <syntaxhighlight lang="c" > .text .global _start .code 32 //表示后续的指令使用ARM指令集 _start: /* 关闭看门狗 */ 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] /* 恢复原来的值 */ /* 怎么从ARM State切换到Thumb State? */ adr r0, thumb_func //定义此标号的地址 add r0, r0, #1 /* bit0=1时, bx就会切换CPU State到thumb state */ bx r0 .code 16 //下面都使用thumb指令集 thumb_func: //需要得到这个标号的地址 /*下面就是使用thumb指令来执行程序*/ bl sdram_init //bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */ /* 重定位text, rodata, data段整个程序 */ bl copy2sdram /* 清除BSS段 */ bl clean_bss //bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ ldr r0, =main /* 绝对跳转, 跳到SDRAM ,先把main的地址赋值给R0 */ mov pc, r0 /*让后再移动到PC*/ halt: b halt </syntaxhighlight> 上传代码编译测试 出现错误,如下 init.o(.text+0x6c):In function 'sdram_init2'; undefined reference to 'memcpy' 发现是init,o里sdram——init2使用的了memcpy函数 查看init.c <syntaxhighlight lang="c" > #include "s3c2440_soc.h" void sdram_init(void) { BWSCON = 0x22000000; BANKCON6 = 0x18001; BANKCON7 = 0x18001; REFRESH = 0x8404f5; BANKSIZE = 0xb1; MRSRB6 = 0x20; MRSRB7 = 0x20; } #if 0 /************************************************************************** * 设置控制SDRAM的13个寄存器 * 使用位置无关代码 **************************************************************************/ void memsetup(void) { unsigned long *p = (unsigned long *)MEM_CTL_BASE; p[0] = 0x22111110; //BWSCON p[1] = 0x00000700; //BANKCON0 p[2] = 0x00000700; //BANKCON1 p[3] = 0x00000700; //BANKCON2 p[4] = 0x00000700; //BANKCON3 p[5] = 0x00000700; //BANKCON4 p[6] = 0x00000700; //BANKCON5 p[7] = 0x00018005; //BANKCON6 p[8] = 0x00018005; //BANKCON7 p[9] = 0x008e07a3; //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4 p[10] = 0x000000b2; //BANKSIZE p[11] = 0x00000030; //MRSRB6 p[12] = 0x00000030; //MRSRB7 } #endif </syntaxhighlight> /*下面函数使用了memcpy函数,显然是编译器的操作,使用了memcpy把数组里的值从代码段拷贝到了arr局部变量里 是否可以禁用掉memcpy*/ <syntaxhighlight lang="c" > void sdram_init2(void) { unsigned int arr[] = { 0x22000000, //BWSCON 0x00000700, //BANKCON0 0x00000700, //BANKCON1 0x00000700, //BANKCON2 0x00000700, //BANKCON3 0x00000700, //BANKCON4 0x00000700, //BANKCON5 0x18001, //BANKCON6 0x18001, //BANKCON7 0x8404f5, //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4 0xb1, //BANKSIZE 0x20, //MRSRB6 0x20, //MRSRB7 }; volatile unsigned int * p = (volatile unsigned int *)0x48000000; int i; for (i = 0; i < 13; i++) { *p = arr[i]; p++; } } </syntaxhighlight> 文章说没有什么方法禁用memecpy但是可以修改这些变量 比如说将其修改为静态变量,这些数据就会放在数据段中,最终重定位时会把数据类拷贝到对应的arr地址里面去 <syntaxhighlight lang="c" > void sdram_init2(void) { const static unsigned int arr[] = { //加上const 和static 0x22000000, //BWSCON 0x00000700, //BANKCON0 0x00000700, //BANKCON1 0x00000700, //BANKCON2 0x00000700, //BANKCON3 0x00000700, //BANKCON4 0x00000700, //BANKCON5 0x18001, //BANKCON6 0x18001, //BANKCON7 0x8404f5, //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4 0xb1, //BANKSIZE 0x20, //MRSRB6 0x20, //MRSRB7 }; volatile unsigned int * p = (volatile unsigned int *)0x48000000; int i; for (i = 0; i < 13; i++) { *p = arr[i]; p++; } } </syntaxhighlight> 拷贝进行实验 得出bin文件有1.4k左右 ![](https://i.imgur.com/0RY4sBN.png) 查看之前的文件使用ARM指令集是2K左右 ![](https://i.imgur.com/yei76DQ.png) 查看反汇编代码 <syntaxhighlight lang="c" > sdram.elf: file format elf32-littlearm Disassembly of section .text: /*前面这些ARM指令还是占用4个字节*/ 30000000 <_start>: 30000000: e3a00453 mov r0, #1392508928 ; 0x53000000 30000004: e3a01000 mov r1, #0 ; 0x0 30000008: e5801000 str r1, [r0] 3000000c: e3a00313 mov r0, #1275068416 ; 0x4c000000 30000010: e3e01000 mvn r1, #0 ; 0x0 30000014: e5801000 str r1, [r0] 30000018: e59f005c ldr r0, [pc, #92] ; 3000007c <.text+0x7c> 3000001c: e3a01005 mov r1, #5 ; 0x5 30000020: e5801000 str r1, [r0] 30000024: ee110f10 mrc 15, 0, r0, cr1, cr0, {0} 30000028: e3800103 orr r0, r0, #-1073741824 ; 0xc0000000 3000002c: ee010f10 mcr 15, 0, r0, cr1, cr0, {0} 30000030: e59f0048 ldr r0, [pc, #72] ; 30000080 <.text+0x80> 30000034: e59f1048 ldr r1, [pc, #72] ; 30000084 <.text+0x84> 30000038: e5801000 str r1, [r0] 3000003c: e3a01000 mov r1, #0 ; 0x0 30000040: e5910000 ldr r0, [r1] 30000044: e5811000 str r1, [r1] 30000048: e5912000 ldr r2, [r1] 3000004c: e1510002 cmp r1, r2 30000050: e59fd030 ldr sp, [pc, #48] ; 30000088 <.text+0x88> 30000054: 03a0da01 moveq sp, #4096 ; 0x1000 30000058: 05810000 streq r0, [r1] 3000005c: e28f0004 add r0, pc, #4 ; 0x4 30000060: e2800001 add r0, r0, #1 ; 0x1 30000064: e12fff10 bx r0 30000068 <thumb_func>: 30000068: f94ef000 bl 30000308 <sdram_init> 3000006c: f9fef000 bl 3000046c <copy2sdram> 30000070: fa24f000 bl 300004bc <clean_bss> /**下面的thumb指令占据2个字节**/ 30000074: 4805 ldr r0, [pc, #20] (3000008c <.text+0x8c>) 30000076: 4687 mov pc, r0 30000078 <halt>: 30000078: e7fe b 30000078 <halt> 3000007a: 0000 lsl r0, r0, #0 3000007c: 0014 lsl r4, r2, #0 3000007e: 4c00 ldr r4, [pc, #0] (30000080 <.text+0x80>) 30000080: 0004 lsl r4, r0, #0 30000082: 4c00 ldr r4, [pc, #0] (30000084 <.text+0x84>) 30000084: c011 stmia r0!,{r0, r4} 30000086: 0005 lsl r5, r0, #0 30000088: 1000 asr r0, r0, #0 3000008a: 4000 and r0, r0 3000008c: 04fd lsl r5, r7, #19 3000008e: 3000 add r0, #0 </syntaxhighlight> 如果你的flash很小的话可以考虑使用Thumb指令集 烧写进去看是否可以运行 测试结果没有任何问题 Thumb指令集后面没有任何作用,只是简单作为介绍 =第004节_und异常模示程序示例= 写一个程序故意让其发生未定义异常,让后处理这个异常 查看uboot中源码 uboot\u-boot-1.1.6\cpu\arm920t 打开start.S <syntaxhighlight lang="c" > /*code: 28 -- 72*/ #include <config.h> #include <version.h> /* ************************************************************************* * * Jump vector table as in table 3.1 in [1] * ************************************************************************* */ #define GSTATUS2 (0x560000B4) #define GSTATUS3 (0x560000B8) #define GSTATUS4 (0x560000BC) #define REFRESH(0x48000024) #define MISCCR (0x56000080) #define LOCKTIME 0x4C000000 /* R/W, PLL lock time count register */ #define MPLLCON 0x4C000004 /* R/W, MPLL configuration register */ #define UPLLCON 0x4C000008 /* R/W, UPLL configuration register */ #define CLKCON 0x4C00000C /* R/W, Clock generator control reg. */ #define CLKSLOW 0x4C000010 /* R/W, Slow clock control register */ #define CLKDIVN 0x4C000014 /* R/W, Clock divider control */ /******下面这些就是异常向量表*****/ .globl _start _start: 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 _undefined_instruction: .word undefined_instruction _software_interrupt: .word software_interrupt _prefetch_abort: .word prefetch_abort _data_abort: .word data_abort _not_used: .word not_used _irq: .word irq _fiq: .word fiq .balignl 16,0xdeadbeef </syntaxhighlight> 手册异常向量表定义 ![](https://i.imgur.com/4iuxNLR.png) 接下来我们写程序 <syntaxhighlight lang="c" > .text .global _start _start: b reset /* vector 0 : reset */ //一上电复位,是从0地址开始执行,跳到reset处 b do_und /* vector 4 : und */ //如果发生未定义指令异常,就会跳到0x04地址未定义指令异常处,执行do_und程序 /*假设一上电从0地址开始执行,reset,做一系列初始化之后 *故意加入一条未定义指令 und_code: .word 0xdeadc0de /* 未定义指令 */ 当CPU发现无法执行此条指令时,就会发生未定义指令异常,就会执行do_und bl print2, */ do_und: /* 执行到这里之前: * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址 * 2. SPSR_und保存有被中断模式的CPSR * 3. CPSR中的M4-M0被设置为11011, 进入到und模式 * 4. 跳到0x4的地方执行程序 */ //需要从新设置sp栈,指向某一块没有使用的地址 /* sp_und未设置, 先设置它 */ ldr sp, =0x34000000 /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */ /* 发生异常时,当前被中断的地址会保存在lr寄存器中 先减后存*/ /* lr是异常处理完后的返回地址, 也要保存 */ stmdb sp!, {r0-r12, lr} /* 保存现场 */ /* 处理und异常 */ mrs r0, cpsr//把cpsr的值读入r0 ldr r1, =und_string//把下面的字符串地址赋值给r1 bl printException /* 这些寄存器保存在栈中,把他读取出来就可以了*/ /* 恢复现场 */ /* 先读后加*/ /* 把r0 ~ r12的值从栈中都取出来,并且把原来保存的lr值,赋值到pc中去*/ ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */ </syntaxhighlight> /* *如何定义字符串,可以百度搜索 arm-linux-gcc 汇编 定义字符串 * *官方的说明文档 *http://web.mit.edu/gnu/doc/html/as_7.html .string "str" Copy the characters in str to the object file. You may specify more than one string to copy, separated by commas. Unless otherwise specified for a particular machine, the assembler marks the end of each string with a 0 byte. You can use any of the escape sequences described in section Strings. 我们使用.str会自动加上结束符 */ <syntaxhighlight lang="c" > und_string: .string "undefined instruction exception" 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 bl uart0_init bl print1 /* 故意加入一条未定义指令 */ und_code: .word 0xff123456 /* 未定义指令 */ bl print2 //bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ ldr pc, =main /* 绝对跳转, 跳到SDRAM */ halt: b halt </syntaxhighlight> 如何处理这个异常呢? 直接print打印一句话,新建一个 exception.c文件 <syntaxhighlight lang="c" > #include "uart.h" void printException(unsigned int cpsr, char *str) //cpsr打印相应的寄存器,str打印一个字符串 { puts("Exception! cpsr = ");\\打印cpsr printHex(cpsr);//输出cpsr的值 puts(" ");//输出空格 puts(str);//输出str值 puts("\n\r");//回车,换行 } </syntaxhighlight> 我们打开之前编译过的程序的反汇编文件 里面一定包含了保存恢复 <syntaxhighlight lang="c" > 30000084 <delay>: 30000084: e1a0c00d mov ip, sp 30000088: e92dd800 stmdb sp!, {fp, ip, lr, pc} //保存 d是减 b是存 3000008c: e24cb004 sub fp, ip, #4 ; 0x4 30000090: e24dd004 sub sp, sp, #4 ; 0x4 30000094: e50b0010 str r0, [fp, #-16] 30000098: e51b3010 ldr r3, [fp, #-16] 3000009c: e2433001 sub r3, r3, #1 ; 0x1 300000a0: e50b3010 str r3, [fp, #-16] 300000a4: e51b3010 ldr r3, [fp, #-16] 300000a8: e3730001 cmn r3, #1 ; 0x1 300000ac: 0a000000 beq 300000b4 <delay+0x30> 300000b0: eafffff8 b 30000098 <delay+0x14> 300000b4: e89da808 ldmia sp, {r3, fp, sp, pc}//恢复,先读后加 </syntaxhighlight> 上传编译 修改makefile添加文件 <syntaxhighlight lang="c" > all: start.o led.o uart.o init.o main.o exception.o #arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf 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 $@ $< *.dis </syntaxhighlight> 编译成功烧写 没有输出我们想要的字符串 很多同学想学会如何调试程序 这里我们演示 <syntaxhighlight lang="c" > sdram: bl print1 //添加print1 /* 故意加入一条未定义指令 */ und_code: .word 0xdeadc0de /* 未定义指令 */ bl print2 //添加print2,实现这两个函数,来打印 //bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ ldr pc, =main /* 绝对跳转, 跳到SDRAM */ halt: b halt 实现print1 print2这两个打印函数,在uart.c这个文件里 void print1(void) { puts("abc\n\r"); } void print2(void) { puts("123\n\r"); } 上传代码烧写,发现print1 print2并未执行成功 发现在start.S并未初始化 uart0_init(),删除main.c中的uart0_init()初始化函数 ldr pc, =sdram sdram: bl uart0_init bl print1 /* 故意加入一条未定义指令 */ und_code: .word 0xff123456 /* 未定义指令 */ bl print2 //bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ ldr pc, =main /* 绝对跳转, 跳到SDRAM */ halt: b halt </syntaxhighlight> 加上uart0_init,再次编译烧写 程序正常运行,print1 print2全部打印,表明未定义指令并未运行,难道这个地址是一个已经定义的地址 打开2440芯片手册 找到ARM指令集 ![](https://i.imgur.com/ZSBOZZU.png) 发现竟然是SWI指令,CPU可以识别出来,他不是一条未定义指令 我们得找到一条CPU不能识别的指令,定义为0x03000000 <syntaxhighlight lang="c" > ldr pc, =sdram sdram: bl uart0_init bl print1 /* 故意加入一条未定义指令 */ und_code: .word 0x03000000 /* 未定义指令 */ bl print2 //bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ ldr pc, =main /* 绝对跳转, 跳到SDRAM */ halt: b halt </syntaxhighlight> 编译烧写执行 打印了未定义指令异常CPSR地址,打印了字符串,最后执行main函数 .word 0xdeadcode /* 也是一条未定义指令 只要指令地址对不上上表就是未定义指令*/ 我们查看下cpsr是否处于未定义模式 bit[4:0]表示CPU模式 11011 果然处于und模式 我们看看这个程序做了什么事情 <syntaxhighlight lang="c" > .text .global _start /*一上电复位,从0地址开始执行 跳到 reset: 做了一系列初始化 当执行到0xdeadc0de这条指令时候,CPU根本就不知道这条指令什么意思 und_code: .word 0xdeadc0de /* 未定义指令 */ bl print2 让后就发生未定义指令异常,他会把下一条指令的地址保存到异常模式的LR寄存器 /* 执行到这里之前已经发生了很多事情 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址 * 2. SPSR_und保存有被中断模式的CPSR * 3. CPSR中的M4-M0被设置为11011, 进入到und模式 * 4. 跳到0x4的地方执行程序 * * 设置栈 sp是指und的地址 * sp_und未设置, 先设置它 * /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */ * lr是异常处理完后的返回地址, 也要保存 */ * 保存现场 */ * 处理und异常 */ * 恢复sp * cpu就会切换到之前的模式 */ */ .text .global _start _start: b reset /* vector 0 : reset */ b do_und /* vector 4 : und */ und_addr: .word do_und do_und: /* 执行到这里之前: * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址 * 2. SPSR_und保存有被中断模式的CPSR * 3. CPSR中的M4-M0被设置为11011, 进入到und模式 * 4. 跳到0x4的地方执行程序 */ /* sp_und未设置, 先设置它 */ ldr sp, =0x34000000 /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */ /* lr是异常处理完后的返回地址, 也要保存 */ stmdb sp!, {r0-r12, lr} /* 保存现场 */ /* 处理und异常 */ mrs r0, cpsr ldr r1, =und_string bl printException /* 恢复现场 */ ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */ und_string: .string "undefined instruction exception" </syntaxhighlight> 程序改进 源程序 <syntaxhighlight lang="c" > .text .global _start _start: b reset /* vector 0 : reset */ /*使用b命令跳转 相对跳转*/ b do_und /* vector 4 : und */ do_und: /* 执行到这里之前: * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址 * 2. SPSR_und保存有被中断模式的CPSR * 3. CPSR中的M4-M0被设置为11011, 进入到und模式 * 4. 跳到0x4的地方执行程序 */ /* sp_und未设置, 先设置它 */ ldr sp, =0x34000000 /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */ /* lr是异常处理完后的返回地址, 也要保存 */ stmdb sp!, {r0-r12, lr} /* 保存现场 */ /* 处理und异常 */ mrs r0, cpsr ldr r1, =und_string /*这里又使用bl指令跳转,如果是nand启动,这个函数在4k之外,这个函数必定出错 为了保险,跳转到sdram中执行程序*/ bl printException /* 恢复现场 */ ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */ und_string: .string "undefined instruction exception" 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 bl uart0_init bl print1 /* 故意加入一条未定义指令 */ und_code: .word 0xdeadc0de /* 未定义指令 */ bl print2 //bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ ldr pc, =main /* 绝对跳转, 跳到SDRAM */ halt: b halt </syntaxhighlight> 改进后代码 <syntaxhighlight lang="c" > .text .global _start _start: b reset /* vector 0 : reset */ /*跳转到sdram执行这个函数,那么这个函数一定在sdram中 我们需要指定让他去前面这块内存去读这个值,担心如果这个文件很大,超过4Knand就没法去读这个文件*/ ldr pc, und_addr /* vector 4 : und */ /*增加如下 查看反汇编,在08的地址读让后跳到3c*/ und_addr: .word do_und do_und: /* 执行到这里之前: * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址 * 2. SPSR_und保存有被中断模式的CPSR * 3. CPSR中的M4-M0被设置为11011, 进入到und模式 * 4. 跳到0x4的地方执行程序 */ /* sp_und未设置, 先设置它 */ ldr sp, =0x34000000 /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */ /* lr是异常处理完后的返回地址, 也要保存 */ stmdb sp!, {r0-r12, lr} /* 保存现场 */ /* 处理und异常 */ mrs r0, cpsr ldr r1, =und_string bl printException /* 恢复现场 */ ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */ und_string: .string "undefined instruction exception" /**如果你的程序长度稍有变化,就不能保证运行 加上 .align 4才能保证后面的程序以4字节对齐,保证程序运行 **/ .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 /*把链接地址赋值给pc 直接就跳转到sdram中*/ 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 </syntaxhighlight> 看一下整个程序的执行过程 ![](https://i.imgur.com/SWYt6w0.png) ='''《《所有章节目录》》'''= <categorytree mode=all background-color:white;">ARM裸机加强版</categorytree> [[Category:ARM裸机加强版 ]]
返回至
第014课 异常与中断
。
导航
导航
WIKI首页
官方店铺
资料下载
交流社区
所有页面
所有产品
MPU-Linux开发板
MCU-单片机开发板
Linux开发系列视频
单片机开发系列视频
所有模块配件
Wiki工具
Wiki工具
特殊页面
页面工具
页面工具
用户页面工具
更多
链入页面
相关更改
页面信息
页面日志