1、关于虚拟地址和物理地址:
物理地址(记为PA)是设计生产的时候确定的,是一种硬件编码生产的物理地址。虚拟地址对于操作系统或者软件层面来说的。虚拟地址与物理地址之间有一个虚拟地址转换层建立了一个从虚拟地址映射到物理地址的映射表(也就是映射原理中所说的页表)。物理地址是根据具体需求来确定大小的,通常情况下会比虚拟地址小。
例如:对于 32 为 ARM 处理器,最大寻址空间为 4GB(2^32),但是物理空间并没有配置到这么大,所以对于虚拟地址(记为VA) 来说,其地址为 0x00000000~0xFFFFFFFF,对于 物理地址(记为PA) 来说,其地址应小于或等于 0xFFFFFFFF。
2、地址映射原理:
把虚拟地址划分为一定大小空间的存储块,同样,物理空间也划分为同样大小的块。然后,依照存储块的大小,可分为:1、段(1MB) 2、大页(64KB) 3、小页(4KB)4、极小页(1KB)。第一种称为段模式,后面三种称为页模式。这些映射,都是通过页表实现的,页表又可可以分为:一级页表(用于段模式)和 二级页表(用于页模式)
3、什么是页表(转换表)呢?
页表就是存储在内存中(会被拷贝到 SDRAM 中存放,以供 MMU 查询),用于表示 VA 与 PA的映射关系的一个表格。表格中每项称为条目,条目里的内容称为描述符(段描述符和页描述符)
宏观上理解转换表:整个转换表可以看作是一个int类型的数组,数组中的一个元素就是一个表索引和表项的单元。数组中的元素值就是表项,这个元素的数组下标就是表索引。
4、uboot中虚拟地址映射采用了段模式:
以段模式映射时,因为 VA 大小位 4GB,段模式每个条目表为 1MB 大小的空间,因此一个映射单元只能管1MB内存,所以全映射时,条目总数(全映射时页表所占内存空间) =4GB / 1MB =4096 单元(每条 32 位,即 4 字节,共 4 KB)。也就是说这个数组的元素个数是4096.实际上我们做的时候并没有依次单个处理这4096个单元,而是把4096个分成几部分,然后每部分用for循环做相同的处理。下面便是uboot的lowlevel_init.S文件的593行中有这么一段代码,将4G空间分成了4096/256=16个部分,分别进行虚拟地址映射处理。
//uboot的lowlevel_init.S文件的593行中有这么一段汇编代码
.set __base,0 //设置初值为0
// Access for iRAM
.rept 0x100 //汇编语言循环体开始,循环0x100次,也就是256次
FL_SECTION_ENTRY __base,3,0,0,0 //这里的__base数值为0,ap值为3,d、c、b值均为0
.set __base,__base+1 //这里相当于C语言的i=i+1
.endr //循环体结束
对于循环体中。FL_SECTION_ENTRY __base这个宏定义的定义方式如下:
.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
5、对于上面代码的位操作,通过下面这个一级页表来理解
(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: 表示本描述符是表示段模式(段描述符标识)
【补充】uboot中虚拟地址映射表对应的汇编代码,以及每一段代码完成的地址映射如下:
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
// should be accessed
.rept 0xb00 - 0x800
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
/* .rept 0xc00 - 0xb00
.word 0x00000000
.endr */
.set __base,0xB00
.rept 0xc00 - 0xb00
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
// 0xC000_0000鏄犲皠鍒?x2000_0000
.set __base,0x300
//.set __base,0x200
// 256MB for SDRAM with cacheable
.rept 0xD00 - 0xC00
FL_SECTION_ENTRY __base,3,0,1,1
.set __base,__base+1
.endr
// access is not allowed.
@.rept 0xD00 - 0xC80
@.word 0x00000000
@.endr
.set __base,0xD00
// 1:1 mapping for debugging with non-cacheable
.rept 0x1000 - 0xD00
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
【注意】映射后的PA,如果PA数值为0,说明这段物理地址是不能使用的。
结论:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。
思考:为什么配置时将链接地址设置为c3e00000,因为这个地址将来会被映射到33e00000这个物理地址。