L1 page table
L1 page table 将 32位内核的整个 4GB 地址空间划分为 4096 个大小相等的部分(entry),每个部分映射 1 MB 范围内的虚拟内存空间。
每个条目 (entry) 可以保存指向二级转换的地址,也可以保存用于表示这 1MB 虚拟内存对应的物理内存基地址。
L1 转换表中的每个 entry 下的低 2 bit 表示该 entry 类型,有 4 种类型
bit0 | bit1 | 说明 |
---|---|---|
0 | 0 | fault. This can be either a prefetch data abort |
1 | 0 | bit10 ~ bit31 point to 2 level page table |
0 | 1 | bit20 ~ bit31 Section Base Address |
0 | 1 | bit24 ~ bit31 Supersection Base Address |
L1 page table 转换
根据虚拟地址的高 12 bit (32 bit 地址)定位到 L1 page table 中的 entry number,然后计算该 entry 所在的物理地址:L1 page table addr + number * 0x4。
得到 entry 的物理地址后,通过读取该地址的内容获取虚拟地址对应的物理基地址,最终确定物理地址。转换过程如下图:
L2 page table
L2 与 L1 page table 一样,都需要存放到一片物理内存上,L2 table 有 256 个字大小(4字节)的 entry ,需要1KB的内存空间,并且必须与1KB的边界对齐,如果每个 entry 映射 4KB 物理内存,那么一个 L2 table 就映射 1MB 物理内存。
L2 page table 中的每个 entry 下的低 2 bit 表示该 entry 的类型,有 3 种类型:
bit0 | bit1 | 说明 |
---|---|---|
0 | 0 | fault:产生同步异常,可以是预取或数据中止,取决于访问类型 |
1 | 0 | laegr page: 表示该 entry 映射 64KB 的物理地址 (由于每个条目都指向 4KB 的地址空间,因此大页面条目必须重复 16 次) |
XN | 1 | small page: 表示该 entry 映射 4KB 的物理地址 |
L2 page table 转换
L2 page table 的基地址需要通过 L1 page table 中的 entry 得知,就需要使用 L1 page table 中的第二个类型(用于指向 L1 page table 的物理地址)。
根据虚拟地址的高 12 bit (32 bit 地址) 定位到 L1 page table 中的 entry number,然后计算该 entry 所在的物理地址:L1 page table addr + number * 0x4。然后读取出 entry 中的 L2 page table 的物理地址(entry 中的 bit10 ~ bit31)
得知 L2 page table 物理地址后,根据虚拟地址的 bit12 ~ bit19 定位 L2 page table 的 entry number,然后计算出该 entry 的物理地址,该 entry 中记录了该虚拟内存对应的物理基地址,L2 page table entry 格式如下图:
如采用 small page,bit12 ~ bit31 为物理基地址,最后加上虚拟地址的 bit0 ~ bit11 (页内偏移)即为该虚拟地址对应的物理地址。
L2 page table 转换过程如图:
在 rtos 中的应用
系统中可以将 L1 与 L2 同时使用,对于某些地址,如外设的地址,并没有达到 1MB 连续的地址,这时候可以分为更细的粒度,如 4kb 的粒度,这时候将该 L1 entry 指向 L2 table,然后再根据虚拟地址的 bit12 - bit19 来找到 L2 的 entry,因为有 8 bit,所以最大 256。
0x2400fc00 ==> 001001000000 00001111 110000000000
576 15
通过虚拟地址的高 12bit 定位到 L1 页表第 576 号 entry, 该 entry 中记录了 L2 页表的地址,通过读取该页表的高 22 bit 就能获取到 L2 页表的地址(第 10 bit 无效,说明 L2 页表必然 1KB 地址对齐)。
定位到 L2 页表的地址后,再根据虚拟地址的 bit12 ~ bit19 (第 15 个 entry)来定位 L2 页表的 entry,得到 entry 后,该 4 字节的 entry 中就表示了该虚拟地址对应的物理地址,该 entry 是小页表类型,则 bit12 ~ bit31 说明了该虚拟地址对应的起始物理地址(这里有个点,低 12 bit 为 0,说明这个起始物理地址是 4KB 对齐的),得到物理地址之后 + 虚拟地址的第 12 bit。
每个 L1 页表的 entry 能映射 1MB 范围的物理内存,而有时候,芯片的 memmap 可能并不是按照 1MB 来划分,比如 I2c0_base 为 0x90000000, I2c1_base 为 0x90001000,中间其他 IP 的基地址到 0x90010000 就结束了,并没有把这 1MB 完全覆盖,也就是并没有达到 0x90100000。
此时有两个策略:
- 策略一:使用 L1 page table,直接将 0x90000000 这个地址按照 1MB 进行映射,此时 L1 page table 中的一个 entry 就能覆盖到 0x90000000 ~ 0x90100000,此时这 1MB 的内存属性是一样的,有时我们并不希望 0x90010000 ~ 0x90100000 之后的内存被访问,这时候仅使用 L1 page table 就不合适了。
- 策略二:使用 L1 page table + L2 page table,L2 page table 能够更小粒度的映射上面的内存,让每 4KB 为一个 entry,这样子每 4KB 的内存属性是一样的。此时 L1 page table 依然是映射的 1MB,只不过这 1MB 被 L2 page table 进而分成了 256 份。
其实这两种策略都是可以的,只不过第二种策略能够更精细化的管理内存,但初始化的时候略微复杂些,不过也还好了,按照手册的说明来就行了,第一种策略就较为简单粗暴了,但也无伤大雅。
page table 中的每个 entry 中还有其余 bit 可以使用,这些 bit 用来描述该 虚拟/物理 内存的属性,如是否可执行、可读、可写、cacheable、uncacheable 等。