“第014课 异常与中断”的版本间的差异

来自百问网嵌入式Linux wiki
第71行: 第71行:
  
 
---》图2   
 
---》图2   
![](https://i.imgur.com/Yg9tRdq.png)
+
![](需要替换://i.imgur.com/Yg9tRdq.png)
  
 
有CPU 有中断控制器,我们的中断源发送
 
有CPU 有中断控制器,我们的中断源发送
第196行: 第196行:
  
 
usr用户模式(不可直接进入其他模式)  可以编程操作CPSR直接进入其他模式
 
usr用户模式(不可直接进入其他模式)  可以编程操作CPSR直接进入其他模式
![](https://i.imgur.com/cgghj9L.png)
+
![](需要替换://i.imgur.com/cgghj9L.png)
 
这个图是有关各个模式下能访问寄存器的,再讲这个图之前我们先引入 2种state
 
这个图是有关各个模式下能访问寄存器的,再讲这个图之前我们先引入 2种state
  
第221行: 第221行:
 
下节课会演示使用Thumb指令集编译,看是否生成的bin文件会变小很多
 
下节课会演示使用Thumb指令集编译,看是否生成的bin文件会变小很多
  
![](https://i.imgur.com/cgghj9L.png)
+
![](需要替换://i.imgur.com/cgghj9L.png)
 
在每种模式下都有R0 ~ R15
 
在每种模式下都有R0 ~ R15
 
在这张图注意到有些寄存器画有灰色的三角形,表示访问该模式下访问的专属寄存器
 
在这张图注意到有些寄存器画有灰色的三角形,表示访问该模式下访问的专属寄存器
第247行: 第247行:
 
CRSR当前程序状态寄存器,这是一个特别重要的寄存器
 
CRSR当前程序状态寄存器,这是一个特别重要的寄存器
 
SPSR保存的程序状态寄存器,他们格式如下
 
SPSR保存的程序状态寄存器,他们格式如下
![](https://i.imgur.com/P5Tkmq5.png)
+
![](需要替换://i.imgur.com/P5Tkmq5.png)
  
 
首先 M4 ~ M0 表示当前CPU处于哪一种模式(Mode)
 
首先 M4 ~ M0 表示当前CPU处于哪一种模式(Mode)
第253行: 第253行:
 
假如你当前处于用户模式下,是没有权限修改这些位的
 
假如你当前处于用户模式下,是没有权限修改这些位的
 
M4 ~ M0对应什么值,会有说明
 
M4 ~ M0对应什么值,会有说明
![](https://i.imgur.com/YShQWjP.png)
+
![](需要替换://i.imgur.com/YShQWjP.png)
  
 
查看其他位
 
查看其他位
第274行: 第274行:
 
我们来看看发生异常时CPU是如何协同工作的
 
我们来看看发生异常时CPU是如何协同工作的
 
进入异常的处理流程(硬件)
 
进入异常的处理流程(硬件)
![](https://i.imgur.com/rI6ZYwF.png)
+
![](需要替换://i.imgur.com/rI6ZYwF.png)
 
我们来翻译一下:
 
我们来翻译一下:
 
发生异常时,我们的CPU会做什么事情
 
发生异常时,我们的CPU会做什么事情
第285行: 第285行:
  
 
退出异常怎么做?
 
退出异常怎么做?
![](https://i.imgur.com/pJzcOIV.png)
+
![](需要替换://i.imgur.com/pJzcOIV.png)
 
1 让LR减去某个值,让后赋值给PC(PC = 某个异常LR寄存器减去 offset)
 
1 让LR减去某个值,让后赋值给PC(PC = 某个异常LR寄存器减去 offset)
 
减去什么值呢?
 
减去什么值呢?
 
也就是我们怎么返回去继续执行原来的程序,根据下面这个表来取值
 
也就是我们怎么返回去继续执行原来的程序,根据下面这个表来取值
![](https://i.imgur.com/9lPkInF.png)
+
![](需要替换://i.imgur.com/9lPkInF.png)
 
如果发生的是SWI可以把 R14_svc复制给PC
 
如果发生的是SWI可以把 R14_svc复制给PC
 
如果发生的是IRQ可以把R14_irq的值减去4赋值给PC
 
如果发生的是IRQ可以把R14_irq的值减去4赋值给PC
第295行: 第295行:
 
3 清中断(如果是中断的话,对于其他异常不用设置)
 
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指令集后面没有任何作用,只是简单作为介绍
  
  

2018年1月17日 (三) 18:25的版本

第001节_概念引入与处理流程

取个场景解释中断

假设有个大房间里面有小房间,婴儿正在睡觉,他的妈妈在外面看书。 问:这个母亲怎么才能知道这个小孩醒? 1 过一会打开一次房门,看婴儿是否睡醒,让后接着看书

2 一直等到婴儿发出声音以后再过去查看,期间都在读书

第一种 叫做查询方式: 优点:简单 缺点: 累 写程序如何:

while(1)
{
	1 read book(读书)
	2 open door(开门)
	if()
		return(read book)
	else
		照顾小孩
	
}

第二种叫中断方式: 优点:不累 缺点:复杂

写程序:

 

while(1)
{
	read book
	中断服务程序()//如何被调用?
	{
	处理照顾小孩
	}
}

我们还是看看母亲被小孩哭声打断如何照顾小孩?

母亲的处理过程 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 发现有中断/异常产生,开始处理 对于不同的异常,跳去不同的地址执行程序 这地址上,只是一条跳转指令,跳去执行某个函数 (地址) 指的是异常向量 如下就是异常向量表 对于不同的异常都有一条跳转指令


.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代码
//保护现场,调用处理函数,恢复现场

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文件

   
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

对于使用Thumb指令集

   

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

改进

 
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 $@ $<


对start.S需要修改代码

原重定位章节Start.S文件


.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

使用thumb指令集的Start.S文件

 

.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


上传代码编译测试 出现错误,如下 init.o(.text+0x6c):In function 'sdram_init2'; undefined reference to 'memcpy' 发现是init,o里sdram——init2使用的了memcpy函数

查看init.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

/*下面函数使用了memcpy函数,显然是编译器的操作,使用了memcpy把数组里的值从代码段拷贝到了arr局部变量里 是否可以禁用掉memcpy*/

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++;
	}
	
}

文章说没有什么方法禁用memecpy但是可以修改这些变量

比如说将其修改为静态变量,这些数据就会放在数据段中,最终重定位时会把数据类拷贝到对应的arr地址里面去

 
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++;
	}
	
}

拷贝进行实验

得出bin文件有1.4k左右 ![](https://i.imgur.com/0RY4sBN.png)

查看之前的文件使用ARM指令集是2K左右

![](https://i.imgur.com/yei76DQ.png)

查看反汇编代码

 
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

如果你的flash很小的话可以考虑使用Thumb指令集

烧写进去看是否可以运行 测试结果没有任何问题 Thumb指令集后面没有任何作用,只是简单作为介绍