例如在人的生活中,表情有喜怒哀乐.在ARM工作状态中,也有不同的模式如下表所示:
(详细内容请参考 ARM架构参考手册第二章41页)
对应的中文详细模式如下:
今天主要对ARM的 undefined模式来认识ARM中处理异常的机制,在ARM中,如果遇到异常情况,首先会找异常向量表,如下图(详细请参考ARM架构手册54页)
例如,有以下代码。在板子运行的时候就会发生异常。对应的,由于.word这是undifined异常,对应异常向量表,就会在00000004这个位子来寻求解决异常的办法.......
1 #include《stdio.h》 2 3 int main() 4 { 5 6 __asm__ __volatile__( 7 ".word 0x77777777 " 8 ); 9 10 11 12 }
但是。。。。不巧的是在ARM中地址00000000到10000000地址是ROM,ROM是只可读,不可写的。所以,这就是MMU存在的意义之所在了,来一个“隔山打牛”把内存00000004这个地址映射到其他可以写入的地方,以后操作00000004这个地址就是在操作其他地址了。
顺便回顾上一次MMU的代码。详细请参考上一篇笔记
1 2 int (*printf)(char *, ...) = 0xc3e114d8; 3 4 void init_ttb(unsigned long *addr); 5 void enable_mmu(void); 6 7 int main() 8 { 9 10 printf("hello mmu! "); 11 //30000000---40000000 ----> 500000000----600000000 12 unsigned long *pp=0x50001000; 13 *pp= 0x111111111; 14 printf("*pp is%x ",*pp); 15 16 enable_mmu(); 17 18 unsigned long *pv=0x30001000; 19 printf("*pv is %x ",*pv); 20 } 21 22 void init_ttb(unsigned long *addr) 23 { 24 unsigned long va = 0;//定义虚拟地址 25 unsigned long pa = 0;//定义物理地址 26 27 //40000000-------80000000 ==== 40000000------80000000 28 for(va=0x40000000; va<=0x80000000; va+=0x100000){ 29 pa = va; 30 addr[va >> 20] = pa | 2; 31 //|2的目的是将0-2位置为10此时将是小页模式4K 32 } 33 34 //10000000-------14000000 ==== 10000000------14000000 35 for(va=0x10000000; va<=0x14000000; va+=0x100000){ 36 pa = va; 37 addr[va >> 20] = pa | 2; 38 } 39 40 //30000000-------40000000 ==== 50000000------60000000 41 for(va=0x30000000; va<0x40000000; va+=0x100000){ 42 pa = va + 0x20000000; 43 addr[va >> 20] = pa | 2; 44 } 45 } 46 47 void enable_mmu(void) 48 49 { 50 unsigned long addr = 0x60000000; 51 init_ttb(addr); 52 //step:初始化页表 53 54 unsigned long mmu = 1 | (1 << 1) | (1 << 8); 55 //将MMU的第0,1,8位置1 56 __asm__ __volatile__( 57 "mov r0, #3 " 58 "MCR p15, 0, r0, c3, c0, 0 "//manager 59 "MCR p15, 0, %0, c2, c0, 0 "//addr 60 "MCR p15, 0, %1, c1, c0, 0 "// enable mmu 61 : 62 : "r" (addr), "r" (mmu) 63 : "r0" 64 ); 65 printf("MMU is enable! "); 66 } 67
在代码中,首先,在0x 30001000的地址中存入了数据0x11111111.然后,将0x30000000地址映射到了0x50000000的地址中,最后,打印0x50001000地址里的值,就是打印0x30001000的地址里的值。
以下是对于处理异常模式跳转的代码:
1 2 int (*printf)(char *, ...) = 0xc3e114d8; 3 4 void init_ttb(unsigned long *addr); 5 void enable_mmu(void); 6 unsigned long volitale_init(); 7 void memcopy(unsigned long* dest,unsigned long* source,int len); 8 9 int main() 10 { 11 //发生异常时会进入异常模式跳转到0000 0004地址处理异常事件 12 unsigned long source_addr=volitale_init(); 13 //异常事件处理函数 14 printf("souce addr is %x ",source_addr); 15 //将异常处理地址的值放到0x60000004 16 memcopy(0x60000004,source_addr,0x100); 17 18 enable_mmu(); 19 //内存映射将0x00000004映射到0x6000000004 20 __asm__ __volatile__( 21 ".word 0x77777777 " 22 ); 23 printf("welcome back! "); 24 25 26 } 27 28 void memcopy(unsigned long* dest,unsigned long* source,int len) 29 { 30 int i=0;; 31 for(i=0;i<len;i++) 32 dest[i]=source[i]; 33 } 34 35 unsigned long volitale_init() 36 { 37 unsigned long source; 38 __asm__ __volatile__( 39 "ldr %0, =vector_start " 40 : "=r" (source) 41 ); 42 43 44 return source; 45 46 } 47 //跳转要分三部: 48 //1:将PC保存到新模式下的lr中; 49 //2:将CPSR保存在SPSR中 50 //3:初始化SP 51 //前两步由硬件完成,而第三部需要手动完成 52 53 __asm__( 54 "vector_start: " 55 "mov sp, #0x66000000 "//初始化SP 56 "stmfd sp!, {r0-r12, lr} "//初始化sp,入栈保护寄存器 57 58 //打印一句话 59 "ldr r0, =string " 60 "ldr r2, show " 61 "blx r2 " 62 63 "mov sp, #0x66000000 "// 64 "ldmea sp, {r0-r12, pc}^ "// 65 66 67 "loop: " 68 "b loop " 69 70 71 "show: " 72 ".word 0xc3e114d8 " 73 74 "string: " 75 ".asciz "hello undefined\n" " 76 ".align 2 " 77 ); 78 79 void init_ttb(unsigned long *addr) 80 { 81 unsigned long va = 0;//定义虚拟地址 82 unsigned long pa = 0;//定义物理地址 83 84 //40000000-------80000000 ==== 40000000------80000000 85 for(va=0x40000000; va<=0x80000000; va+=0x100000){ 86 pa = va; 87 addr[va >> 20] = pa | 2; 88 //|2的目的是将0-2位置为10此时将是小页模式4K 89 } 90 91 //00000000-------10000000 ==== 60000000------70000000 92 for(va=0x00000000; va<=0x10000000; va+=0x100000){ 93 pa = va+0x60000000; 94 addr[va >> 20] = pa | 2; 95 } 96 97 //10000000-------14000000 ==== 10000000------14000000 98 for(va=0x10000000; va<=0x14000000; va+=0x100000){ 99 pa = va; 100 addr[va >> 20] = pa | 2; 101 } 102 103 //30000000-------40000000 ==== 50000000------60000000 104 for(va=0x30000000; va<0x40000000; va+=0x100000){ 105 pa = va + 0x20000000; 106 addr[va >> 20] = pa | 2; 107 } 108 } 109 110 void enable_mmu(void) 111 112 { 113 unsigned long addr = 0x70000000; 114 init_ttb(addr); 115 //step:初始化页表 116 117 unsigned long mmu = 1 | (1 << 1) | (1 << 8); 118 //将MMU的第0,1,8位置1 119 __asm__ __volatile__( 120 "mov r0, #3 " 121 "MCR p15, 0, r0, c3, c0, 0 "//manager 122 "MCR p15, 0, %0, c2, c0, 0 "//addr 123 "MCR p15, 0, %1, c1, c0, 0 "// enable mmu 124 : 125 : "r" (addr), "r" (mmu) 126 : "r0" 127 ); 128 printf("MMU is enable! "); 129 }
在程序中,
volitale_init()函数的作用就是获得处理异常函数的地址,存到变量source中并返回.
voctor_start汇编函数主要就是实现了从SVR模式跳入到UND模式,在UND模式中打印了一句话
hello undefined并且跳出来在代码中,跳转模式主要有三步,而跳出模式也有两步,代码中没给出,就是 /1:将CPSR保存在SPSR中 2:将PC保存到新模式下的lr中; 。
memcopy()函数的作用是:将source_addr里的数据拷贝到0x60000004地址,一共拷贝len个地址
实际上就是将处理异常函数的地址存到可以读写的地址。下次到0x00000004地址找处理函数的时候久直接调用到了处理异常函数。
接下来是enable_mmu()函数,用法与上一个一样。但是,此时在制表函数init_ttb()中。新把
从00000000----10000000的地址映射到了60000000-----70000000 地址
对于汇编部分,对于不太了解的没关系,主要了解实现功能是什么。掌握主要流程, 到最后再作
统一总结。
Makefile:
1 2 all: 3 arm-none-linux-gnueabi-gcc -c mmu.c -o mmu.o 4 arm-none-linux-gnueabi-ld -Ttext=0x41000000 mmu.o -o mmu 5 arm-none-linux-gnueabi-objcopy -Ielf32-littlearm -Obinary mmu mmu.bin 6 7 clean: 8 rm -rf mmu mmu.o mmu.bin 9 10 11
在PC终端make
在minicom板子上dnw到41000000地址go41000000