1、MMU介绍
在uboot阶段并不是必须要开启MMU(内存管理单元),在没开启MMU前使用的是物理地址,开启MMU后使用的是虚拟地址。
MMU就是在物理内存和应用程序之间添加了一个层次,专门用来管理内存,这样写应用程序的人就不用关心物理内存的细节。
比如32位的机器理论上最大内存为4G,运行的程序以进程为单位,每个进程都认为自己拥有4G的内存,其实分配给该进程的物理内存肯定是没有4G的,进程的虚拟内存有4G。虚拟内存和物理内存都按照相同大小的划分页,虚拟内存的页和物理内存的页建立映射关系,当程序要访问的页不在内存时就会发生缺页异常,然后将磁盘里的页加载到内存中,如果此时物理内存没有空闲的页则还涉及页面置换。使用虚拟内存的好处:访问控制、内存分配和释放提供方便(比如malloc实际可能不是连续的)、运行超过物理内存大小的程序等。
2、虚拟地址和物理地址的转换
虚拟地址和物理地址的转换是依靠转换表,宏观上理解转换表:整个转换表可以看作是一个int类型的数组,数组中的一个元素就是一个表索引和表项的单元。数组中的元素值就是表项,这个元素的数组下标就是表索引。把虚拟地址按照一定规则进行查表,根据表的内容就可以知道对应的物理地址。映射分为段映射和页映射,这里是按照段映射(1M)进行讲解的,就是把内存按照1M为单位进行划分,4G的内存就分为4096个段,每个段对应一个页表项,每个页表项占4字节,页表总共占16KB。只需要构建好页表然后设置TTB,硬件会自动去查询页表进行转换。
3、开启MMU的步骤
(1)使能域访问,域访问是和MMU的访问控制有关的。
(2)构建虚拟地址转换表,并将转换表的基地址赋值到cp15协处理器的c2;
(3)使能MMU;
4、开启MMU的代码
#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
/* 使能域访问,将cp15的c3寄存器设置成0x0000ffff*/
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0 @load domain access register
/* 设置转换表基地址,_mmu_table_base就是转换表的基地址*/
ldr r0, _mmu_table_base
ldr r1, =CFG_PHY_UBOOT_BASE //CFG_PHY_UBOOT_BASE是uboot的链接地址
ldr r2, =0xfff00000
bic r0, r0, r2 //将r0的bit20-bit31清零
orr r1, r0, r1 //将r0的值与r1进行或操作,并将结果保存在r1中
mcr p15, 0, r1, c2, c0, 0 //将r1的值写到cp15协处理器的c2寄存器中
mmu_on:
/* 开启MMU:cp15的c1寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。*/
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1
mcr p15, 0, r0, c1, c0, 0
#endif
#if defined(CONFIG_ENABLE_MMU)
_mmu_table_base:
.word mmu_table //mmu_table就是构建虚拟地址映射表
#endif
5、构建虚拟映射表的代码
/* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b
.word (\base << 20) | (\ap << 10) | \
(\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm
.section .mmudata, "a"
.align 14
// the following alignment creates the mmu table at address 0x4000.
.globl mmu_table
mmu_table:
.set __base,0
// Access for iRAM
.rept 0x100
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
// Not Allowed
.rept 0x200 - 0x100
.word 0x00000000
.endr
.set __base,0x200
// should be accessed
.rept 0x600 - 0x200
FL_SECTION_ENTRY __base,3,0,1,1
.set __base,__base+1
.endr
.rept 0x800 - 0x600
.word 0x00000000
.endr
.set __base,0x800
// should be accessed
.rept 0xb00 - 0x800
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
.set __base,0xB00
.rept 0xc00 - 0xb00
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
.set __base,0x300
.rept 0xD00 - 0xC00
FL_SECTION_ENTRY __base,3,0,1,1
.set __base,__base+1
.endr
.set __base,0xD00
.rept 0x1000 - 0xD00
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
构建页表就是通过多个循环,构建满足特定规则的int型数,因为一个页表项就刚好是4个字节,页表的每个位都有特定含义。
(1)FL_SECTION_ENTRY:这个宏就是用来构建页表项的,具体含义参考一级页表的页表项含义,实际效果就是定义一个特定的word类型数;
(2).rept和.end构成循环语句,比如.rept 0xD00 - 0xC00和.endr,就是循环(0xD00-0xC00)次。
6、一级页表的页表项含义
(1)段基址: 在设计地址映射时,从20bit开始的目的是要映射的物理地址要 1MB 对齐,段基址就是这段1MB 物理地址起始地址的高[31:20]位,每个条目中的描述符的段基址都不一样(以段来说,相差 1MB)。
(2) AP: AP 是用来设置权限的,与 C1 的 R/S 位结合使用。
(3) Domain 域: 不管是段模式还是页模式,系统都把 4GB 空间分为 16 个域,每个域有相同的权限检查(在 C3 设置 ),这里的 Domain 是用来标识本段所在的域
(4) C/B: C/B 位是控制位,与本条目(描述符)所在域的 Cache 和 Buffer 有关(是否允许本域开启 Cache 和 Buffer)
(5) [1:0]=0b10: 表示本描述符是表示段模式(段描述符标识)
7、最终得到的虚拟地址映射表
转换得到的虚拟地址映射表:
VA PA length
0-10000000 0-10000000 256MB
10000000-20000000 0 256MB
20000000-60000000 20000000-60000000 1GB 512-1.5G
60000000-80000000 0 512MB 1.5G-2G
80000000-b0000000 80000000-b0000000 768MB 2G-2.75G
b0000000-c0000000 b0000000-c0000000 256MB 2.75G-3G
c0000000-d0000000 30000000-40000000 256MB 3G-3.25G
d-完 d-完 768MB 3.25G-4G