原文:https://blog.csdn.net/u011003120/article/details/51812188
参考:
https://blog.csdn.net/groundhappy/article/details/54889677
MMU 存储器管理单元,在之前因为是操作物理地址,不需要MMU,因此是处于关闭状态的,而这次则是打开MMU并且使用MMU.
一、MMU的作用
- 1.将虚拟地址转化为物理地址
- 2.进行访问权限的管理
看上图可以得知,有三个运行的程序,他们的虚拟地址都为0x400000,但是若要使用物理地址,他们的物理地址不能够相同,因此就需要一个机制,使他们的相同的虚拟地址对应不同的物理地址,这个机制就是上图中的Page tables(即页表),虚拟地址通过查表的方式对应到不同的物理地址上。
二、地址转化
首先需要知道的是,以段(Section,1M)的方式进行转换时只用到一级页表,而页(Page)的方式进行转换时用到两级页表,有粗也转换和细页转换两种,页的大小有3种:大页(64KB)、小页(4KB)和极小页(1KB)。
1.地址转化总体分析
整个地址转换的过程分为了两步,为一级转换和二级转换。
虚拟地址的[31:20]位作为一个表的索引,表的名字为translation table,即TTB,如果表的后两位为00,则为无效的转换,如上图,如果后两位为01,则表示第二级转换为粗页方式转换,如果为10,则表示接下来会按照段的方式转换,若是11,则第二级为细页方式转换。
2.如何找到一级页表
要想找到一级页表,首先需要知道的是一级页表的地址,即TTB,它是保存在CP15的C2寄存器中,
看上面偷来的图^_^,一共有4096个转换描述符,即虚拟地址的[31:20]一共12位的最大寻址空间,虚拟地址的[31:20]再加上TTB,就是相对应的描述符,这样就找到了虚拟地址对应的描述符。
3.段式转化简单分析
还是上面的图,找到了虚拟地址相对应的地址描述符之后,描述符的[31:20]位便是物理地址的[31:20]位,虚拟地址的[19:0]位便是物理地址的[19:0]位,
至于页的转换方式,则不再细讲。
4.TTB
MMU要自动进行虚拟地址到物理地址的转化,首先要找到一级页表,而一级页表的基地址(TTB:translation table base)则是保存在CP15的C2寄存器中。因此,当程序员创建好相应的页表后,需要将页表基地址写入该寄存器。
把TTB 的值写入CP15 C2寄存器中后,MMU工作的时候,会从C2中取出TTB的值,因此MMU就会知道这张表的基地址,MMU就会工作了。
这张表是放在内存里面的,
这张表是工程师事先建立好的
三MMU的配置与使用
在本次课程中使用段式转化的方式,因此需要做以下几个工作:
建立一级页表,写入TTB,打开MMU.
本实验是通过点亮LED来完成的,LED的寄存器地址是
#define GPMCON (volatile unsigned long *)0x7F008820 #define GPMDAT (volatile unsigned long *)0x7F008824
因此,本实验的目的就是将LED的物理地址映射为虚拟地址
#define GPMCON (volatile unsigned long *)0xA0008820 #define GPMDAT (volatile unsigned long *)0xA0008824
1.建立一个一级页表
如上图,对描述符的每一个寄存器进行讲解。
[1:0] 最后两位固定为10 即使用段式的方式 [2] B 是否使用write buffer [3] C 是否使用CACHE [4] XN设置为1 [8:5] Domain,用来说明该段是属于16个域中的哪一个域, [9] P表示段区间有ECC,ARM11不支持 [11:10] AP:访问权限,这个配合域,说明该段地址的访问权限。 [14:12] TEX 略 [15] APX 略 [16] S 表示是否共享 [17] nG 略 [18] 0 [19] nS 略
各个寄存器的值如下:
#define MMU_FULL_ACCESS (3 << 10) /* 访问权限 [11:10]*/ #define MMU_DOMAIN (0 << 5) /* 属于哪个域 [8:5]*/ #define MMU_SPECIAL (1 << 4) /* 必须是1 [4]*/ #define MMU_CACHEABLE (1 << 3) /* cacheable [3]*/ #define MMU_BUFFERABLE (1 << 2) /* bufferable [2]*/ #define MMU_SECTION (2) /* 表示这是段描述符 [1:0]*/ #define MMU_SECDESC (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_SECTION)
建立页表
void creat_page_table() { unsigned long *ttb = (unsigned long *)0x50000000; //表在内存的基地址处 unsigned long vaddr; //虚拟地址 unsigned long paddr; //物理地址 vaddr = 0xa0000000; //虚拟地址 paddr = 0x7F000000; *(ttb + (vaddr >> 20)) = (paddr&0xfff00000) | MMU_SECDESC; //*(ttb + (vaddr >> 20)) 为表项的位置 //(paddr&0xfff00000) 获取高12位数据 //MMU_SECDESC 访问led的gpio很简单,就不需要cache和buffer }
*(ttb + (vaddr >> 20)) 即为页表的描述符,就是上面所说的 TTB + 虚拟地址[31:20]位,建立了上述的页表后,访问虚拟地址0xA0008824 通过页表,即可找到物理地址0x7F008824。
2设置TTB和使能mmu
设置TTB,其实就是将基地址写入CP15的C2寄存器中,因此使用汇编将其写入。
设置访问权限,就是将域的权限设置为0B11,即不进行权限检查,主要是设置CP15的C3寄存器
使能MMU,即打开MMU
void mmu_init() { __asm__( /*设置TTB*/ "ldr r0, =0x50000000\n" "mcr p15, 0, r0, c2, c0, 0\n" /*不进行权限检查*/ "mvn r0, #0\n" "mcr p15, 0, r0, c3, c0, 0\n" /*使能MMU*/ "mrc p15, 0, r0, c1, c0, 0\n" "orr r0, r0, #0x0001\n" "mcr p15, 0, r0, c1, c0, 0\n" : : ); }
3.内存的映射
关于内存映射这一点不太清楚,大致的理解就是,将虚拟地址和物理地址的 0x50000000 - 0x540000000 这一段进行映射,这样直接操作虚拟地址就相当于操作物理地址。
//映射内存 vaddr = 0x50000000; paddr = 0x50000000; //其虚拟地址和物理地址是一致的 while (vaddr < 0x54000000) //映射64mb { *(ttb + (vaddr >> 20)) = (paddr & 0xFFF00000) | MMU_SECDESC_WB; vaddr += 0x100000; paddr += 0x100000; }
全部代码
/******************************************** *file name: main.c *author : stone *date : 2016.6.30 *function : MMU进行相关的操作 *********************************************/ #define GPMCON (volatile unsigned long *)0xA0008820 //虚拟地址 #define GPMDAT (volatile unsigned long *)0xA0008824 #define MMU_FULL_ACCESS (3 << 10) /* 访问权限 [11:10]*/ #define MMU_DOMAIN (0 << 5) /* 属于哪个域 [8:5]*/ #define MMU_SPECIAL (1 << 4) /* 必须是1 [4]*/ #define MMU_CACHEABLE (1 << 3) /* cacheable [3]*/ #define MMU_BUFFERABLE (1 << 2) /* bufferable [2]*/ #define MMU_SECTION (2) /* 表示这是段描述符 [1:0]*/ #define MMU_SECDESC (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_SECTION) #define MMU_SECDESC_WB (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION) void creat_page_table() { unsigned long *ttb = (unsigned long *)0x50000000; //表在内存的基地址处 unsigned long vaddr; //虚拟地址 unsigned long paddr; //物理地址 vaddr = 0xa0000000; //虚拟地址 paddr = 0x7F000000; *(ttb + (vaddr >> 20)) = (paddr&0xfff00000) | MMU_SECDESC; //*(ttb + (vaddr >> 20)) 为表项的位置 //(paddr&0xfff00000) 获取高12位数据 //MMU_SECDESC 访问led的gpio很简单,就不需要cache和buffer //映射内存 vaddr = 0x50000000; paddr = 0x50000000; //其虚拟地址和物理地址是一致的 while (vaddr < 0x54000000) //映射64mb { *(ttb + (vaddr >> 20)) = (paddr & 0xFFF00000) | MMU_SECDESC_WB; vaddr += 0x100000; paddr += 0x100000; } } void mmu_init() { __asm__( /*设置TTB*/ "ldr r0, =0x50000000\n" /* 页表的基地址 */ "mcr p15, 0, r0, c2, c0, 0\n" /*不进行权限检查*//*cp15 c3 domain 控制权限*/ "mvn r0, #0\n" "mcr p15, 0, r0, c3, c0, 0\n" /*使能MMU*/ "mrc p15, 0, r0, c1, c0, 0\n" "orr r0, r0, #0x0001\n" "mcr p15, 0, r0, c1, c0, 0\n" : : ); } int gboot_main() { //*(GPMCON) = 0x1111; //*(GPMDAT) = 0x00; //1.建立页表 creat_page_table(); //2.写入TTB //3.使能 mmu_init(); *(GPMCON) = 0x1111; *(GPMDAT) = 0x00; return 0; }
来源:https://www.cnblogs.com/lh03061238/p/12625484.html