内存管理单元MMU负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查。
4种映射长度:段(1MB)、大页(64KB)、小页(4KB)、极小页(1KB)。
对每个段都可以设置访问权限。
大页、小页的每个子页(sub-page,即被映射页的1/4)都可以单独设置访问权限。
没有启动MMU时,CPU核、cache、MMU、外设等所有部件使用的都是物理地址。
理论知识我就不多写了,毕竟这里是做实验而不是讲原理的,但是代码的注释会很清楚
主要的c代码:
#include"memmap.h" void creat_page_table() { //我们建立段描述符 相对简单些 便于理解 //权限设置 它们占描述符的低12bit【11-0】 #define MMU_AP (3<<10) //ap位111 访问权限 #define MMU_DOMAIN (0<<5) //选择0域 #define MMU_SPECIAL (1<<4) //必须1 why? #define MMU_CACHEEN (1<<3) //cache使能 #define MMU_BUFFEN (1<<2) //buffer使能 #define MMU_SECTION 2 //0b10 表示这个是段描述符 //段描述符低12bit 没有开启cache和buff,因为它将用于前1M的映射,不能开启cache #define MMU_SEC_DESC MMU_AP|MMU_DOMAIN|MMU_SPECIAL|MMU_SECTION //段描述符低12bit 开启cache和buff,它将用于后面sdram的映射,开启cache buffer #define MMU_SEC_DESC_WB MMU_AP|MMU_DOMAIN|MMU_SPECIAL|MMU_SECTION|MMU_CACHEEN|MMU_BUFFEN unsigned long vir_address,phy_address; unsigned long *mmu_table_base=(unsigned long *)0x30000000;//地址转化为指针 //映射前1M的物理地址到虚拟地址的前1M,为了能够在开启mmu之后0地址的前4k内的程序依然可以执行,所以它们是一一对应的关系 /* (vir_address>>20可以理解取虚拟地址高12bit 也可以理解为虚拟地址除于1MB,就得到了段地址,所以要映射的虚拟地址必须是1M的倍数。学过汇编的话你应该很容易知道段地址是怎么一回事,比如这里我们的是按照1M分段的,那么段1就是1M=0x100000。段大页小页极小页地址,也就是单位不一样。 phy_address&0xfff00000也是保存高12bit 这样做取来的结果是1M的倍数,也是段对应的物理地址,低12bit保存权限,中间的8bit保留 */ vir_address=0; phy_address=0; *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC); /* mmu_table_base+(vir_address>>20)取得段描述符的地址(指针),这4个字节保存着一个32bit的段描述符(((phy_address&0xfff00000) | MMU_SEC_DESC)),段描述符的【31-20】bit保存着段的物理地址(带上单位就是实实在在的地址了,比如12M,12M=0x1200000) MMU_SEC_DESC即是上面定义的权限了 */ /*将0x56000000 之后1M映射到0xA0000000 外部IO存储设备也是不能开启cache和buff的 */ vir_address=0xa0000000; phy_address=0x56000000; *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC); /*将0x30000000 之后64M映射到0xB0000000*/ vir_address=0xb0000000; phy_address=0x30000000; while(phy_address<0x34000000) { *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC_WB); vir_address+=0x100000; //累加1m,到下一个段描述符 phy_address+=0x100000; } } /*以上就是我们建的描述符表了,但是cpu还不知道这是怎么回事,下面我们就需要告诉cpu这样一个虚拟地址映射物理地址的规则了,由于处理开启mmu以后的所有的虚拟地址转换*/ void mmu_init() { unsigned long ttb=0x30000000; //描述符表table的表头地址 __asm__( "mov r0,#0/n" //操作协处理器得仔细看其结构了 "mcr p15,0,r0,c7,c7,0/n" //mcr是写 将r0 写入c7 "mcr p15,0,r0,c7,c10,4/n" "mcr p15,0,r0,c8,c7,0/n" "mov r4,%0/n" "mcr p15,0,r4,c2,c0,0/n" "mvn r0,#0/n" "mcr p15,0,r0,c3,c0,0/n" /*对于控制寄存器 先读出控制寄存器的值 修改之 再保存进去*/ "mrc p15,0,r0,c1,c0,0/n" /*先清除不需要的位 如果需要用到 之后再设置*/ "bic r0,r0,#0x3000/n" "bic r0,r0,#0x0300/n" "bic r0,r0,#0x0087/n" //清除1 2 3 7bit /*设置需要的位*/ "orr r0,r0,#0x2/n" //开启对齐检查 "orr r0,r0,#0x4/n" //开启dcache "orr r0,r0,#0x1000/n" //开启icache "orr r0,r0,#0x1/n" //使能mmu "mcr p15,0,r0,c1,c0,0/n" : :"r" (ttb) ); }
对于协处理器的结构,我也很模糊,以后专门研究下
其次就是定义那些寄存器的物理地址需要修改一下,因为mmu开启之后 就只认虚拟地址了
//led fanction #define GPBCON (*(volatile unsigned long *)0xa0000010) //先将地址值其转化为指针,GPBCON其实就变成了这个地址储存的值鸟 哈哈 #define GPBDAT (*(volatile unsigned long *)0xa0000014) #define GPBUP (*(volatile unsigned long *)0xa0000018) //key addr #define GPFCON (*(volatile unsigned long *)0xa0000050) #define GPFDAT (*(volatile unsigned long *)0xa0000054) #define GPFUP (*(volatile unsigned long *)0xa0000058)
然后就是start.s加的一点内容了
@led start @2010-01-12 @jay .globl _start _start: b reset @预留着以后扩展中断向量表 reset: /*因为下面有c函数 要先设置个sp*/ ldr sp,=4096 @disable watchdog ldr r0,=0x53000000 mov r1,#0 str r1,[r0] @init SDRAM bl memsetup @cope the code to sdram bl cp_to_SDRAM @setup pagetable bl creat_page_table @init and enable mmu bl mmu_init @reset stack 开启了mmu,我们要重设下堆栈指针,指向虚拟地址映射的顶部 ldr sp,=0xb4000000 @跳到虚拟地址 /*这2句指令是从uboot学来的 注意这一点的理解,_led_on是个标签,标签的实质就是个地址,这个标签存储的内容就是另外一个标签led_test(是不是很熟悉的感觉?对了,就是c语言的指针的指针),其实就是将_led_on的内容led_text赋给了pc*/ ldr pc,_led_on _led_on:.word led_test load_sd: ldr sp,=0x34000000 bl led_test @copy to sdram cp_to_SDRAM: @ adr r0,_start mov r0,#0 add r1,r0,#4096 ldr r2,=0x30004000 @注意这里不要指定到0x30000000了 lp: ldmia r0!,{r3-r11} stmia r2!,{r3-r11} cmp r0,r1 ble lp mov pc,lr
最后就是连接脚本了
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . =0x30004000; . =ALIGN(4); .text : { start.o (.text) low_init.o (.text) *(.text) } . = ALIGN(4); .data :{ *(.data) } . =ALIGN(4); .rodata : { *(.rodata) } . =ALIGN(4); __bss_start = .; .bss :{*(.bss)} _end = . ; }
这个也是从uboot学来的
ALIGN(4)是4字节对齐,32bitcpu一次处理32bit的嘛
.是指当前地址 这里指定的知识编译地址,跟运行地址会不一样的。
两个.指令要用;隔开的,注意. =0x30004000 这里有空格的啊 否则会出错
makefile
CC=arm-linux-gcc LD=arm-linux-ld CP=arm-linux-objcopy DP=arm-linux-objdump objs:=start.o low_init.o memmap.o led.o mmu.bin:$(objs) $(LD) -Tboot.lds -g -o mmu_elf $^ $(CP) -O binary -S mmu_elf $@ $(DP) -D -m arm mmu_elf > mmu.asm #makefile 注意 .s不可以大写 -T不可以少- %.o:%.c $(CC) -g -Wall -c -o $@ $< %.o:%.s $(CC) -g -Wall -c -o $@ S< clean: rm -f *.asm *.bin *_elf *.o
内容虽然不多,却花费了我很多时间去理解,其他的代码与之前的一样 没有改动
终于是可以成功开启mmu了
Jay
2010-01-21 11:29:57