MMU 存储器管理单元,在之前因为是操作物理地址,不需要MMU,因此是处于关闭状态的,而这次则是打开MMU并且使用MMU.
看上图可以得知,有三个运行的程序,他们的虚拟地址都为0x400000,但是若要使用物理地址,他们的物理地址不能够相同,因此就需要一个机制,使他们的相同的虚拟地址对应不同的物理地址,这个机制就是上图中的Page tables(即页表),虚拟地址通过查表的方式对应到不同的物理地址上。
首先需要知道的是,以段(Section,1M)的方式进行转换时只用到一级页表,而页(Page)的方式进行转换时用到两级页表,有粗也转换和细页转换两种,页的大小有3种:大页(64KB)、小页(4KB)和极小页(1KB)。
整个地址转换的过程分为了两步,为一级转换和二级转换。
虚拟地址的[31:20]位作为一个表的索引,表的名字为translation table,即TTB,如果表的后两位为00,则为无效的转换,如上图,如果后两位为01,则表示第二级转换为粗页方式转换,如果为10,则表示接下来会按照段的方式转换,若是11,则第二级为细页方式转换。
要想找到一级页表,首先需要知道的是一级页表的地址,即TTB,它是保存在CP15的C2寄存器中,
看上面偷来的图^_^,一共有4096个转换描述符,即虚拟地址的[31:20]一共12位的最大寻址空间,虚拟地址的[31:20]再加上TTB,就是相对应的描述符,这样就找到了虚拟地址对应的描述符。
还是上面的图,找到了虚拟地址相对应的地址描述符之后,描述符的[31:20]位便是物理地址的[31:20]位,虚拟地址的[19:0]位便是物理地址的[19:0]位,
至于页的转换方式,则不再细讲。
MMU要自动进行虚拟地址到物理地址的转化,首先要找到一级页表,而一级页表的基地址(TTB:translation table base)则是保存在CP15的C2寄存器中。因此,当程序员创建好相应的页表后,需要将页表基地址写入该寄存器。
把TTB 的值写入CP15 C2寄存器中后,MMU工作的时候,会从C2中取出TTB的值,因此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: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。
设置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;
}
菜鸟一枚,如有错误,多多指教。。。