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、一级页表的页表项含义

x86机器bios里面怎么关闭iommu bios里的iommu开还是关_物理内存

(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