4.1 页表

4.1.1 页表映射

img

MMU的作用,主要是完成地址的翻译,即虚拟地址到物理地址的转换,无论是main-memory地址(DDR地址),还是IO地址(设备device地址),在开启了MMU的系统中,CPU发起的指令读取、数据读写都是虚拟地址,在ARM Core内部,会先经过MMU将该虚拟地址自动转换成物理地址,然后在将物理地址发送到AXI总线上,完成真正的物理内存、物理设备的读写访问。

img

  • 高位是1的虚拟地址空间,使用TTBR1_ELx基地址寄存器进行页表翻译;
  • 高位是0的虚拟地址空间,使用TTBR0_ELx基地址寄存器页表翻译。
  • Linux Kernel 使用0xffff_0000_0000_0000 - 0xffff_ffff_ffff_ffff作为内核地址区间

img

AArch64支持的页面大小有:4kb、16kb、64kb。不同页面大小的索引情况不同:

  • 4KB:48位有效虚拟地址,每一级页表使用虚拟地址中的9个bit来索引,所以每一级页表就有2^9=512个页表项
  • 16KB:48位有效虚拟地址,L0页表使用VA[47]作为索引,L1页表使用VA[46:36]来索引,可以索引2048个页表项,L2页表使用VA[35:25]来索引,同理也是可以索引2048个页表项,L3页表就指向16kb的页面地址
  • 64KB:三级页表,L1页表使用VA[47:42]索引,只能索引64个L2页表

L0~L2页表项描述符

img

以4kb为例子:

img

  • 处理器根据页表基地址控制寄存器和虚拟地址来判断使用哪个页表基地址寄存器 TTBRO 还是 TTBR1。当虚拟地址第 63 位简称 VA[63] 为 1 时选择 TTBR1;当 VA[63] 为 0 时选择 TTBR0。页表基地址寄存器中存放着 1 级页表
  • 处理器将 VA[47:39] 作为 L0 索引,在 1 级页表(L0 页表)中找到页表项,1 级页表有 512 个页表项
  • 1 级页表的页表项中存放着 2 级页表(L1 页表)的物理基地址。处理器将 VA[38] 作为 L1 索引,在 2 级页表中找到相应的页表项,2 级页表有 512 个页表项
  • 2 级页表的页表项中存放着 3 级页表(L2 页表)的物理基地址。处理器以 VA[29:21] 作为 L2 索引,在 3 级页表(L2 页表)中找到相应的页表项,3 级页表有 512 个页表项
  • 3 级页表的页表项中存放着 4 级页表(L3 页表)的物理基地址。处理器以 VA[20:12] 作为 L3 索引,在 4 级页表(L3 页表)中找到相应的页表项,4 级页表有 512 个页表项
  • 4 级页表的页表项里存放着 4KB 页面的物理基地址,然后加上 VA[11:0],就构成了新的物理地址,因此处理器就完成了页表的查询和翻译工作

4.1.2 页表项****描述符

从下图可知,AArch64 架构页表分成 4 级页表,每一级页表都有页表项,我们把它们称为页表项描述符,每个页表项描述符占 8 字节,那么这些页表项描述符的格式和内容是否都一样?

AArch64 架构中 L0 ~ L3 页表项描述符的格式不完全一样,其中 L0 ~ L2 页表项的内容比较类似,如下图所示。

img

L0~L2 页表项根据内容可以分成 3 类,一是无效的页表项,二是块(block)类型的页表项,三是页表(table)类型的页表项。

  • 当页表项描述符 Bit[0] 为 1 时,表示有效的描述符;当 Bit[0] 为 0 时,表示无效描述符
  • 页表项描述符 Bit[1] 用来表示类型
    • 页表类型:当 Bit[1] 为 1 时,表示该描述符包含了指向下一级页表的基地址,是一个页表类型的页表项
    • 块类型,当 Bit[1] 为 0 时表示一个大内存块(memory block)的页表项,其中包含了最终的物理地址。大内存块通常是用来描述大的连续的物理内存,如 2MBit 或者 1GB 大小的连续物理内存
  • 在块类型的页表项中,Bit[47:n] 表示最终输出的物理地址
    • 若页面粒度是 4KB,在 L1 页表项描述符中 n 为 30,表示 1GB 大小的连续物存。在 L2 页表项描述符中 n 为 21,用来表示 2MB 大小的连续物理内存
    • 若页面粒度为 16KB,在 L2 页表项描述符中 n 为 25,用来表示 32MB 大小的连续物理内存
  • 在块类型的页表项中,Bit[11:2] 是低位属性(lower attribute),Bit[63:52] 是高位属性 (upper attribute)
  • 在页表类型的页表项描述符中,Bit[47:m] 用来指向下一级页表的基地址
    • 当页面粒度为 4KB 时 m 为 12
    • 当页面粒度为 16KB 时 m 为 14
    • 当页面粒度为 64KB 时 m 为 16

L3 页表项描述符包含 5 种页表项,分别是无效的页表项、保留的页表项 4KB 粒度的页表项、16KB 粒度的页表项、64KB 粒度的页表项。

img

L3 页表项描述符的格式如下。

  • 当页表项描述符 Bit[0] 为 1 时,表示有效的描述符;当 Bit[0] 为 0 时,表示无效描述符
  • 当页表项描述符 Bit[1] 为 0 时,表示保留页表项;当 Bit[1] 为 1 时,表示页表类型的页表项
  • 页表描述符 Bit[11:2] 是低位属性,Bit[63:51] 是高位属性
  • 页表描述符中间的位域中包含了输出地址(output address),也就是最终物理页面的高地址段
    • 当页面粒度为 4KB 时输出地址为 Bit[47:12]
    • 当页面粒度为 16KB 时输出地址为 Bit[47:14]
    • 当页面粒度为 64KB 时输出地址为 Bit[47:16]

img

4.1.3 内存****属性

img

  • 执行许可:Unprivileged eXucute Never(UXN)和PXN
  • AF: access flag
  • SH: shareable attributed
  • AP: access permission
  • NS: security bit, 仅在EL3和Secure EL1使用
  • Indx是到MAIR_ELn的索引

https://carlyleliu.github.io/LinuxKernel/LinuxMemoryARM64linux/

内存属性分为2类:

Normal型:sram或者dram那样的内存空间,一般都是过cache的(当然也可不过cache,如外设访问的地址空间,标记为NC)

device型:设备寄存器那样的io空间,都不会过cache。Device属性的内存空间还有下面三种子属性,都有打开和关闭的定义。

  • G(gather:对多个memory的访问可以合并) nG与之相反
  • R(Reordering:对内存访问指令进行重排) nR与之相反
  • E(Early Write Acknowledgement hint:写操作的ack可提早应答) nE与之相反

MAIR寄存器定义如下:

img

内存页表属性部分可以选择这个寄存器的某个index范围作为自己的属性。所以需要操作系统提前预设好MAIR寄存器的各个Attr的值,每个Attr占据8个bit,具体的配置如下表所示

img

根据dd oooo iiii 的不同,对应的属性也不同,具体的配置如下三个表

img

img

img

4.2 MMU Stage 2 Translation

4.2.1 Arch64虚拟化

img

4.2.2 Stage2地址转换

img

OS控制一组转换表,这组转换表将虚拟地址空间映射到它认为的物理地址空间。但是,这个过程需要经过第二阶段转换才可以转化为真实的物理空间。第二阶段由hypervisor控制。

OS控制的阶段称为stage1转换,hypervisor控制的阶段称为stage2转换。OS认为的物理地址空间即为中间物理地址空间IPA。

img

img

4.3 Xhyper内存虚拟化

4.3.1 内存虚拟化原理

在 ARM64 中每个异常等级下都有一个独立的转换空间,有自己独立的虚拟地址:

  • EL0 的虚拟地址转换由 OS 中指向 TTBR0_EL1 的页表控制;
  • EL1 的虚拟地址转换由 OS 中指向 TTBR1_EL1 的页表控制;
  • EL2 Hypervisor 的虚拟地址转换由指向 TTBR_EL2 的页表控制;

img

在Xhyper的实现中没有开启EL2的虚拟地址转换,这意味着Xhyper直接使用物理内存地址

img

在虚拟化环境中,如果 EL1/EL0 可以直接观测到物理地址空间,不管是直接访问还是通过页表的虚拟地址转换,那么多个虚拟机就无法共存。所以引入了Stage2的地址转换,hyperviosr 控制对应vm级别可使用的内存,通过指向由 VTTBR_EL2 控制的页表来实现 Stage 1阶段可见的地址空间到实际物理地址空间的映射关系,这样通过在切换虚拟机, VM 时切换 VTTBR_EL2 就可以控制每个虚拟机,VM 实际使用的物理地址空间了。

img

Hypervisor 创建每个虚拟机 VM 对应的二阶段地址转换

img

Stage1和Stage2页表的结构是相同的,Geust Os 的虚拟地址通过 TTBR0_EL1 或者 TTBR1_EL1 转换为 IPA,IPA 再通过 VTTBR_EL2 转换为最终的物理地址

img

4.2.2 mmu初始化

//vmm.c
void stage2_mmu_init(void)
{
LOG_INFO("Stage2 Translation MMU initialization ...\n");

/* Physical Address range supported */
u64 feature;
read_sysreg(feature, id_aa64mmfr0_el1);
LOG_INFO("PARange bits is %d\n", PA_RANGE(feature));

/* 配置 VTCR_EL2 寄存器
* T0SZ = 64 - 20 = 44 : (IPA range is 2^(64-20) = 2^44)
* SL0 = 2 : starting level is leve-0
* TG0 = 0 : 4K Granule size
* NSW = 1 : Non-Secure World
* NSA = 1 : Non-Secure Access
* PS = 1 : Physical address Size is 36 bits
*/
u64 vtcr = VTCR_T0SZ(20) | VTCR_SL0(2) |
VTCR_SH0(0) | VTCR_TG0(0) | VTCR_NSW |
VTCR_NSA | VTCR_PS(4);

LOG_INFO("Setting vtcr_el2 to 0x%x\n", vtcr);
write_sysreg(vtcr_el2, vtcr);

/* 配置内存属性
Attr0(索引 0):对应 Device-nGnRnE 内存类型,值为 0x0。
Attr1(索引 1):对应 Normal Non-Cacheable 内存类型,值为 0x44。
*/
u64 mair = (DEVICE_nGnRnE << (8 * DEVICE_nGnRnE_INDEX)) | (NORMAL_NC << (8 * NORMAL_NC_INDEX));
LOG_INFO("Setting mair_el2 to 0x%x\n", mair);
write_sysreg(mair_el2, mair);

isb();

return;
}
  • 设置vtcr_el2寄存器,设置IPA地址范围大小、Stage-2 转换表的起始级别、 Stage-2 转换的页面粒度等
  • 设置内存属性,写入mair_el2寄存器中,Normal Non-Cacheable 内存类型 = 普通内存语义(允许乱序、推测)+ 禁止缓存,常用于 DMA 缓冲区、共享内存或需要避免缓存一致性问题的场景。

4.2.3 Hypervisor初始化

//vmm.c
/* Provides configuration controls for virtualization */
void hyper_setup()
{
/* 配置hcr_el2寄存器
* HCR_RW :虚拟机运行在 AArch64
* HCR_VM :开启stage2地址转换
*/
u64 hcr = HCR_RW | HCR_VM;
LOG_INFO("Setting hcr_el2 to 0x%x and enable stage 2 address translation\n");
write_sysreg(hcr_el2, hcr);
isb();
}

4.2.4 映射虚拟机

首先是内存映射函数,为虚拟机的虚拟地址范围 [va, va + size) 创建 Stage-2 页面映射,映射到物理地址 [pa, pa + size)。

  • pgt:Stage-2 页面表的基地址(通常由 VTTBR_EL2 提供)。
  • va:虚拟机的虚拟地址(Virtual Address)。
  • pa:物理地址(Physical Address),要映射到的目标地址。
  • size:映射的大小(字节)
  • mattr:内存属性索引,指向 MAIR_EL2 的某个 Attr(例如 DEVICE_nGnRnE 或 NORMAL_NC)
/*  为虚拟机的虚拟地址范围 [va, va + size) 创建 Stage-2 页面映射,映射到物理地址 [pa, pa + size)。
pgt:Stage-2 页面表的基地址(通常由 VTTBR_EL2 提供)。
va:虚拟机的虚拟地址(Virtual Address)。
pa:物理地址(Physical Address),要映射到的目标地址。
size:映射的大小(字节)。
mattr:内存属性索引,指向 MAIR_EL2 的某个 Attr(例如 DEVICE_nGnRnE 或 NORMAL_NC)
*/
void create_guest_mapping(u64 *pgt, u64 va, u64 pa, u64 size, u64 mattr)
{
/* va, pa, size shall be page alignment */
if(va % PAGESIZE != 0 ||
pa % PAGESIZE != 0 ||
size % PAGESIZE != 0) {
abort("Create_guest_mapping with invalid param");
}

for(u64 p = 0; p < size; p += PAGESIZE, va += PAGESIZE, pa += PAGESIZE) {
u64 *pte = page_walk(pgt, va, true);
if(*pte & PTE_AF) {
abort("Page table entry has been used");
}
*pte = PTE_PA(pa) | PTE_AF | mattr | PTE_V;
}
}

创建页表,分配内存函数

/*
遍历 Stage-2 页表(从 L0 到 L2),找到或创建 L3 页表项。
*/
u64 *page_walk(u64 *pgt, u64 va, bool alloc)
{
/* page walk from L0 ~ L2 */
for(int table_level = 0; table_level < 3; table_level ++) {
// 每级通过 PINDEX(table_level, va)计算索引,从虚拟地址提取对应级的索引位。
u64 *pte = &pgt[PINDEX(table_level, va)];

/* Page table entry has been mapped to next-level Page table */
if((*pte & PTE_VALID) && (*pte & PTE_TABLE)) {
/* Next-Table address has been alloced
* 63 47 12 2 1 0
* +--------+--------------------------+----------+---+---+
* | | Next-level table addr | IGNORED | 1 | 1 |
* +--------+--------------------------+----------+---+---+
*/
/* Get next-level page table address */
pgt = (u64 *)PTE_PA(*pte);
} else if(alloc == true) {
/* Page table entry is not mapped, alloc one page from next-level page table */
pgt = alloc_one_page();
if(pgt == NULL) {
abort("Unable to alloc one page for page_walk");
}
*pte = PTE_PA(pgt) | PTE_TABLE | PTE_VALID;
} else {
return NULL;
}
}

/* L3 PTE */
return &pgt[PINDEX(3, va)];
}

img