首页 > 解决方案 > ARMv8a 中这个简单的分页是如何工作的

问题描述

根据ARM手册:

在 4kB 颗粒的情况下,硬件可以使用 4 级查找过程。48 位地址有 9 个地址位,每个转换级别(即每个 512 个条目),最后 12 位选择 4kB 中直接来自原始地址的字节。

512 条目 L0 表中的虚拟地址索引的位 [47:39]。这些表条目中的每一个都跨越 512GB 范围并指向一个 L1 表。在该 512 条目 L1 表中,位 [38:30] 用作选择条目的索引,每个条目指向 1GB 块或 L2 表。

位 [29:21] 索引到 512 条目 L2 表,每个条目指向 2MB 块或下一个表级别。在最后一级,位 [20:12] 索引到一个 512 条目的 L2 表,每个条目指向一个 4kB 块

这对我来说 100% 有意义。L0、L1、L2 表和最终到达物理地址的偏移量。

但是,请查看此代码:https ://github.com/bztsrc/raspi3-tutorial/blob/abaf5a5b2bc1a9fdfe5a9d8191c061671555da3d/10_virtualmemory/mmu.c#L66,解释如下:

因为我们选择 4k 作为页面大小,并且一个翻译条目是 8 字节,这意味着我们在每一页上有 512 个条目。因此,索引 0..511 属于第一页,512..1023 属于第二页,依此类推。换句话说,paging[0]的地址等于_end(第一页),paging[512]等于_end + PAGESIZE(第二页)。

看起来它正在设置手册中提到的 L0、L1 和 L2。所以前 512 个条目将是 L0 表的条目,513-1024 个条目将是 L1,1025-1536 条目将是 L2 表。

但是在代码中它开始这样做:

paging[4*512+511]=(unsigned long)((unsigned char*)&_end+5*PAGESIZE) |   // physical address
        PT_PAGE |     // we have area in it mapped by pages
        PT_AF |       // accessed flag
        PT_KERNEL |   // privileged
        PT_ISH |      // inner shareable
        PT_MEM;       // normal memory

索引4*512+511 = 2559远远超出了我想象的 L2 表。我想我误解了一些非常错误的东西!

是否应该跨越第一个表 (L0),然后跨越paging[0]第二个表 (L1)并跨越最后一个表 (L2)?paging[511]paging[512]paging[2013]paging[1024]paging[2559]

r<<21和,这些是什么r*PAGESIZE意思?

标签: carmarmv8

解决方案


有两个表,分别由 TTBR0 和 TTBR1 指向。

第一个,TTBR0,直接指向 &paging[0],并形成 L0,L1,L2 页面层次结构:

Paging[0] points at &paging[512*2]
Paging[512*2] points at &paging[512*3]
Paging[512*3..512*3+511] contains page descriptors for physical memory at 0..200000.

另外

Paging[512*2+1..512*2+511] contains large descriptors for physical memory at 400000..40000000

第二个(内核)TTBR1 直接指向 &paging[512],形成类似的 L0,L1,L2 层次结构:

Paging[512+511] points at &paging[512*4]
Paging[512*4+511] points at &paging[512*5]
Paging[512*5] contains a descriptor for MMIO_BASE+0x201000.

第二组偏移到每个表的第 511 个描述符的原因是使其位于非常高的地址。

虚拟地址解码由翻译控制寄存器的T1SZ选择;它被注释为 3 级或 39 位虚拟寻址:12 位偏移和 27 位表索引(9 位 * 3 级)。

传统上,地址位 63..40 必须具有相同的值——全零或全一。这可以在控制寄存器中放松,但无论如何,位 63 选择 TTBR[01] 中的哪一个将用于选择两个 L0 页表集之一。

传统上,每个进程都有自己的 TTBR0,内核将有一个用于所有进程的 TTBR1 [因此无需更改]。


推荐阅读