根据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 块或下一个表级别。在最后一层,bits [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 个条目。因此 indeces 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
Run Code Online (Sandbox Code Playgroud)
索引4*512+511 = 2559远远超过我想象的 L2 表。我想我误解了一些非常错误的东西!
是否应paging[0]和paging[511]跨越所述第一表(L0),然后paging[512]和paging[2013]跨越第二表(L1)和paging[1024]与paging[2559]跨越最后表(L2)?
怎么样r<<21和r*PAGESIZE,这分别意味着什么?
有两个表,分别由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.
Run Code Online (Sandbox Code Playgroud)
另外
Paging[512*2+1..512*2+511] contains large descriptors for physical memory at 400000..40000000
Run Code Online (Sandbox Code Playgroud)
第二个(内核)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.
Run Code Online (Sandbox Code Playgroud)
第二组偏移到每个表的第511个描述符的原因是使其位于非常高的地址。
虚拟地址译码由翻译控制寄存器的T1SZ选择;它被注释为3级,或39位虚拟寻址:12位偏移量和27位表索引(9位*3级)。
地址位 63..40 传统上必须具有相同的值——全零或全一。这可以在控制寄存器中放宽,但无论如何,位 63 选择 TTBR[01] 中的哪一个将用于选择两个 L0 页表集之一。
传统上,每个进程都有自己的 TTBR0,内核有一个 TTBR1,用于所有进程[因此不需要更改]。