疯雨-版权所有,转载请注明【http://blog.csdn.net/u010346967】
1.mmu的功能
mmu中文名内存管理单元,主要功能是实现虚拟地址到物理地址的转换,同时为内存保护提供硬件上的保证。这里提到了3个概念:虚拟地址、物理地址和内存保护。首先看看什么是虚拟地址(VA)和物理地址(PA)?物理地址是存储单元对应的实际地址,而虚拟地址顾名思义就是软件程序能表达的非物理的实体地址。没有启用mmu之前,访问的地址都是物理地址,我们访问0地址,这么写(int *)0x0 ,启用mmu将物虚拟地址0x5000_0000映射到物理地址0x0,则访问0地址可以这样写(int *)0x5000_0000。那么这么做有什么意义呢?因为程序是运行在固定的地址空间上,那么同一个程序运行两次的话,必然会造成地址空间冲突,这个工作就由mmu来做。我们打开qq程序,调用的都是同一个地址(虚拟地址),而第二次运行时却被定为到另一个物理地址,避免地址冲突,也就是mmu能对虚拟地址进行重映射。多个程序正常运行在不同的进程空间,然后发生异常后就有可能影响其他进程空间,这时候就需要一种硬件上的内存保护机制,mmu实现进程空间的隔离。
2.虚拟地址到物理地址的转换过程
怎么实现一个映射关系呢?如果是个数学题我想你会这么做,VA=f(PA)。在ARM平台我们定义了一张表来描述VA与PA 的映射关系,也就是页表。
这里只介绍段页表(1M的粒度)。用一个32位的数据来描述页表,这就是页表描述符。程序中定义了gen_pte函数来生成一个页表描述符,base为段地址,如内存的起始段地址为0x500,左移20位(增加5个0)得到0x50000000,d为域设置位,c为cache设置位,b为write buffer设置位。
unsigned int gen_pte(unsigned int base,unsigned int ap,unsigned int d,unsigned int c,unsigned int b){return ((base<<20)|(ap<<10)|(d<<5)|(1<<4)|(c<<3)|(b<<2)|(1<<1));}
4G的虚拟地址空间理论上可以分为4G/1M=2^10=4096个段,要查找某个虚拟地址属于哪个段,只需将虚拟地址右移20位。比如要找0x5000_0000的所属段,0x5000_0000/1M=0x5000_0000/2^10=0x500,这里就得到个有用的信息:虚拟地址的前12位表示的是段基址。而后20位刚好可以描述1M的内存。
3.创建段页表
这里得解决两个问题。一、段页表存放在哪里。二、段页表的构建。
首先段页表存在内存空间,放在内存哪里呢?程序员来指定存放地址。这里段页表存放的起始地址为0x5070_0000。映射页表有两段:从0地址开始的2.5G地址空间和内存区(0x5000_0000开始的256M地址空间)。好,存放页表的起始地址有了,那么具体怎么存放呢?当然是起始地址+偏移地址。
可以这样做,分三步:
3.1 用gen_pte函数生成一个页表项。
3.2 找到页表项存放的地址,就是页表起始地址+偏移地址。
偏移地址怎么计算呢?这里定义了一个宏#define PTE_INDEX(va) ((va<<20)>>18)
首先得清楚,偏移地址是根据虚拟地址得到的。将虚拟地址的段地址如0x500左移20位,得到0x5000_0000,再右移18位得到偏移量0x1400。也就是说段地址左移2位(或者说乘以4)就得到了偏移地址。页表起始地址与偏移地址与运算就可得到页表项存放地址。这里定义了一个函数生成页表项存放地址
unsigned int gen_pte_addr(unsigned int pte_base,unsigned int va){return ((pte_base&0xffffc000)|PTE_INDEX(va));}
3.3 将页表项存进页表项地址
*(volatile unsigned int *)pte_addr = pte;
4.把要映射的地址空间进行mmu初始化
void init_mmu(void){unsigned int pte;unsigned int pte_addr;int j;for(j=0;j<0xa00;j++) //0xa00表示2.5G的地址空间{pte = gen_pte(0x0+j,3,0,0,0);pte_addr = gen_pte_addr(0x50700000,0x0+j);*(volatile unsigned int *)pte_addr = pte;}for(j=0;j<0x100;j++) //0x100即256,内存为256M,一个页表项表示1M的空间故循环256次{pte = gen_pte(0x500+j,3,0,1,1);pte_addr = gen_pte_addr(0x50700000,0x500+j);*(volatile unsigned int *)pte_addr = pte;}}
5.启动mmu
void start_mmu(void){unsigned int ttb = 0x50700000; //页表起始地址asm("mcr p15,0,%0,c2,c0,0\n" //通过cp15协处理器,写入页表起始地址到c2"ldr r0,=0x0000ffff\n""mcr p15,0,r0,c3,c0,0\n" //通过c3进行域设置"mov r0,#1\n""mcr p15,0,r0,c1,c0,0\n" //设置c1开启mmu"mov r0,r0\n""mov r0,r0\n""mov r0,r0\n""mov r0,r0\n":: "r"(ttb): "r0");}
下面是具体的代码实现:
#define rGPMCON ((volatile unsigned long *)0x7f008820)
#define rGPMDAT ((volatile unsigned long *)0x7f008824)
#define PTE_INDEX(va) ((va<<20)>>18)
void light_led(void);
unsigned int gen_pte(unsigned int base,unsigned int ap,unsigned int d,unsigned int c,unsigned int b);
unsigned int gen_pte_addr(unsigned int pte_base,unsigned int va);
void init_mmu(void);
void start_mmu(void);
int gboot_main()
{
init_mmu();
start_mmu();
light_led();
return 0;
}
void light_led(void)
{
*rGPMCON = 0x1111;
*rGPMDAT = 0xe;
}
//鐢熸垚涓€涓〉琛ㄩ」
unsigned int gen_pte(unsigned int base,unsigned int ap,unsigned int d,unsigned int c,unsigned int b)
{
return ((base<<20)|(ap<<10)|(d<<5)|(1<<4)|(c<<3)|(b<<2)|(1<<1));
}
unsigned int gen_pte_addr(unsigned int pte_base,unsigned int va)
{
return ((pte_base&0xffffc000)|PTE_INDEX(va));
}
void init_mmu(void)
{
unsigned int pte;
unsigned int pte_addr;
int j;
for(j=0;j<0xa00;j++)
{
pte = gen_pte(0x0+j,3,0,0,0);
pte_addr = gen_pte_addr(0x50700000,0x0+j);
*(volatile unsigned int *)pte_addr = pte;
}
for(j=0;j<0x100;j++)
{
pte = gen_pte(0x500+j,3,0,1,1);
pte_addr = gen_pte_addr(0x50700000,0x500+j);
*(volatile unsigned int *)pte_addr = pte;
}
}
void start_mmu(void)
{
unsigned int ttb = 0x50700000;
asm(
"mcr p15,0,%0,c2,c0,0\n"
"ldr r0,=0x0000ffff\n"
"mcr p15,0,r0,c3,c0,0\n"
"mov r0,#1\n"
"mcr p15,0,r0,c1,c0,0\n"
"mov r0,r0\n"
"mov r0,r0\n"
"mov r0,r0\n"
"mov r0,r0\n"
:
: "r"(ttb)
: "r0"
);
}

810

被折叠的 条评论
为什么被折叠?



