MMU配置和使用
2022-05-30 来源:eefocus
一:初识MMU
MMU是memory managerment unit 即内存管理单元,是把虚拟内存转化为物理内存的一个“介质”,为什么要用到虚拟内存呢,因为在嵌入式系统中,进程和程序是很多的,物理内存根本不够用,所以使用MMU可以最大限度减少物理内存的使用,提高运行效率。MMU也是嵌入式和单片机两者中不同点最大的地方,单片机是没有MMU这个概念的。在这里,我们尝试去做一个简单的程序。假设保存为a。
#include int a=1; void main() { while(1) { printf('&a=%p,a=%dn',&a,a); sleep(3); } } 首先在虚拟机上运行这个程序后在我的主机上显示的是0x80496a4 a=1 再将此文件复制一次保存为b。修改b,将int a=1改为int b=2。 #include int b=1; void main() { while(1) { printf('&b=%p,b=%dn',&b,b); sleep(3); } } 运行后发现显示的是0x80496a4 b=2 为什么读出来的地址一样,一个读出的是1,一个读出的是2呢? 这就是MMU的作用了,这个地址0x80496a4其实是虚拟地址,MMU需要将其转化为物理地址。 二:MMU深入学习 MMU 页表描述 为了支持段和页的映射方式,MMU使用两级页表描述符,一级页表描述符决定访问的是一个分段还是一个分页式的表,如果访问的是一个分页式的页表,处理器MMU决定页表类型是大页还是小页并找到二级页表。 一级页表描述符格式分析 如上图所示: bit[1:0]: 映射类型,分段式还是分页式。 00 忽略 11 无效,返回Translation fault 10 分段式 01 分页式 nG : 0 转换表被标记为全局的; 1 转换表属于特定进程 S : 共享位,0 非共享; 1 共享内存 XN: 0 包含可执行代码; 1 不包含可执行代码 APX,AP位: 权限访问控制位 Doman : 域标识,属于哪个域 P: ECC校验 TEX,C,B: 此区域是否采用缓冲buffer,cache,还是直接访问,一般外设采用无缓冲,内存采用缓冲方式(个人理解) NS:No-Secure 属性 二级页表描述符格式分析 bit[1:0] : 01 时,采用粗粒度大页64K进行映射 1X时,采用小(细)页4K进行映射 1M分段式映射 页表基地址(TTBRx寄存器中)18bits[31:14]+虚拟地址12bits[31:20] +2[00] 构成了一级页表描述符的地址 取得了一级页表描述符的地址,访问这个地址从中可以得到真实的物理段基地址[31:20] 将得到的物理段基地址[31:20]加上虚拟地址中的[19:0]位偏移地址构成了真正的物理地址 每个虚拟地址可以索引 2^12 个一级描述符地址,每个一级描述符可以包含 2^20 个物理地址,总共可以索引4G空间 三:代码实现 所需要进行的工作包括以下三点 1 建立一级页表 2 写入TTB (写入TTB,因为MMU通过cp15寄存器的c2找到表项,来进行后续的工作) 3 打开MMU 一: 根据一级页表描述符,表的基地址在内存起始位置,表项的前十二位Section base address为物理基地址,段描述:section base address是物理地址高十二位,SBZ为should be zero,AP为访问权限(access peimission) ,Domain为域,C为cache,B为write buffer,最后两位10表示段式映射。表项需要设置段式还是分页式,权限,域这里选择为0,由于访问GPIO是很简单的,所以不需要cache和write buffer。 二: c2是设置TTB的寄存器。TTB实际上是页表的基地址,在这里页表放在内存首地址上,写入c2寄存器中。所以这里只要把首地址写入c2寄存器中即可。 三: 使能MMU,cp15的C1寄存器的第0位使能MMU,将其设置为1即可。 设置域的访问权限,每个域的访问权限是由c3寄存器控制的,决定每个域的S和R位,在这里设置为11,意思是不去检查他的访问权限,这里把所有的位都设置为1即可。以下是c3寄存器的描述。 参考:http://www.jianshu.com/p/faebd7feb218 以下为代码实现: 虚拟地址 找到物理地址,然后通过物理地址点亮led 在这里虚拟地址选择为0xA0000000,映射到物理地址0x7f000000,需要页表建立映射关系,一般放在内存的起始地址,6410为0x50000000, #define GPKCON (volatile unsigned long*)0xA0008820 #define GPKDAT (volatile unsigned long*)0xA0008824 /* * 用于段描述符的一些宏定义 */ #define MMU_FULL_ACCESS (3 << 10) /* 访问权限 */ #define MMU_DOMAIN (0 << 5) /* 属于哪个域 */ #define MMU_SPECIAL (1 << 4) /* 必须是1 */ #define MMU_CACHEABLE (1 << 3) /* cacheable */ #define MMU_BUFFERABLE (1 << 2) /* bufferable */ #define MMU_SECTION (2) /* 表示这是段描述符 */ #define MMU_SECDESC (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_SECTION) //这里访问GPIO是很简单的不需要cache和write buffer #define MMU_SECDESC_WB (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION) //对GPIO进行映射 void create_page_table(void) { unsigned long *ttb = (unsigned long *)0x50000000; unsigned long vaddr, paddr; vaddr = 0xA0000000; paddr = 0x7f000000; *(ttb + (vaddr >> 20)) = (paddr & 0xFFF00000) | MMU_SECDESC;//取虚拟地址的高20位,物理基地址高十二位, //内存映射,采用虚拟地址和物理地址相同,用while循环这里选择映射64M空间(不需要太多),这里需要打开cache和write buffer,每次映射好后加上1M,对下一段地址进行映射 vaddr = 0x50000000; paddr = 0x50000000; while (vaddr < 0x54000000) { *(ttb + (vaddr >> 20)) = (paddr & 0xFFF00000) | MMU_SECDESC_WB; vaddr += 0x100000; paddr += 0x100000; } } void mmu_init() { __asm__( /*设置TTB*/ 'ldr r0, =0x50000000n' 'mcr p15, 0, r0, c2, c0, 0n' /*不进行权限检查*/ 'mvn r0, #0n' 'mcr p15, 0, r0, c3, c0, 0n' /*使能MMU*/ 'mrc p15, 0, r0, c1, c0, 0n' 'orr r0, r0, #0x0001n' 'mcr p15, 0, r0, c1, c0, 0n' : : ); } int gboot_main() { create_page_table(); mmu_init(); *(GPKCON) = 0x1111; *(GPKDAT) = 0xe; return 0; }