4、页表项格式:ARM64页表项(PTE)的位布局

ARM64 架构的页表项(PTE)是一个 64 位的数据结构,它不仅是虚拟地址到物理地址的映射桥梁,还携带了内存属性、访问权限、状态标志等关键信息。Linux 内核在遵循 ARM 架构规范的基础上,对部分软件位进行了自定义,以实现更精细的内存管理。理解 PTE 的位布局是掌握页表遍历、缺页异常处理以及内存属性控制的基础。

4.1 ARM64 PTE 总体位布局

一个标准的 ARM64 页表项(以 4KB 页面粒度为例)的 64 位被划分为多个字段。下表列出了从高位到低位的核心位域及其功能:

位域范围 名称 功能描述
[63:52] PBHA / 软件保留 页属性提示扩展(Page Based Hardware Attributes)或由软件自定义使用。
[51:48] Output Address[47:44] 物理地址高位扩展(48位物理地址空间的高4位)。
[47:16] Output Address[47:16] 物理页帧号(PFN)的高32位,与低位组合形成完整的物理地址。
[15:12] Output Address[15:12] 物理页帧号(PFN)的低4位,与高位组合形成完整的物理地址。
[11] nG 非全局位(Not Global)。若置1,该页表项仅在当前ASID的TLB中有效。
[10] AF 访问标志位(Access Flag)。硬件在首次访问时置1,若为0则触发访问标志故障。
[9] SH[1] 可共享性高位(Shareability)。与低位组合表示不可共享、内部共享或外部共享。
[8] SH[0] 可共享性低位。
[7:6] AP[2:1] 访问权限位(Access Permission)。控制读/写权限以及EL0/EL1的访问。
[5] NS 非安全位(Non-Secure)。用于安全世界与非安全世界的内存隔离。
[4] AttrIndx[0] 属性索引低位(Attribute Index)。指向MAIR寄存器中定义的属性。
[3] AttrIndx[1] 属性索引高位。
[2] UXN 用户不可执行(Unprivileged eXecute Never)。EL0无法执行该页。
[1] PXN 特权不可执行(Privileged eXecute Never)。EL1无法执行该页。
[0] Valid 有效位。若为0,表示该页表项无效,硬件遍历时会触发缺页异常。
关键理解: 物理地址字段(Output Address)在 4KB 页面下占据 [47:16] 和 [15:12] 共 36 位,支持 48 位物理地址空间。对于 64KB 页面,低位偏移会有所不同,但高位字段布局保持一致。

4.2 核心属性位详解

4.2.1 访问权限位(AP[2:1])

AP 位决定了页面的读写权限以及异常等级(EL0/EL1)的访问策略。这是内核实现用户态/内核态隔离的基础。

AP[2:1] EL1(内核)权限 EL0(用户)权限 典型用途
00 读/写 无访问 内核私有数据段
01 读/写 读/写 用户进程数据段
10 只读 无访问 内核只读数据(如常量区)
11 只读 只读 用户进程只读数据(如代码段)

4.2.2 脏位与访问位(Dirty / Access Flag)

ARM64 硬件并不直接提供传统的“脏位”(Dirty Bit),而是通过 访问标志位(AF) 和 脏状态管理机制(DBM, Dirty Bit Management) 来模拟。当硬件使能 DBM 时,硬件在写入操作时会自动将 PTE 标记为脏。

  • AF(位[10]): 硬件在首次访问(读或写)时自动置1。若 AF 为0,硬件会触发 访问标志故障,内核在缺页处理中置位 AF 后重试指令。
  • 脏状态(软件模拟): 在 Linux 内核中,当 DBM 未使能时,脏位通过 PTE 的软件位(如 PTE_DIRTY)来记录。硬件写入时,内核通过写保护(将 PTE 设为只读)捕获写操作,然后在缺页处理中置脏位并赋予写权限。
性能提示: 启用硬件 DBM 和 AF 管理可以显著减少缺页异常次数,因为硬件直接管理访问和脏状态,无需内核软件介入。ARMv8.1 及以后架构推荐使用硬件脏位管理。

4.2.3 执行权限位(UXN / PXN)

这两个位提供了比传统 NX(No-Execute)更细粒度的控制:

  • UXN(位[2]): 用户不可执行。若置1,EL0(用户态)无法从该页执行指令。
  • PXN(位[1]): 特权不可执行。若置1,EL1(内核态)无法从该页执行指令。

内核通常将用户栈和数据段标记为 UXN=1,防止用户态代码执行数据段;将内核某些敏感区域(如 vmalloc 区域)标记为 PXN=1,防止内核漏洞利用。

4.3 内存属性与缓存策略

PTE 中的 AttrIndx[1:0](位[4:3])是一个 2 位索引,指向系统寄存器 MAIR_EL1(Memory Attribute Indirection Register)中定义的 8 个属性槽之一。每个槽定义了该页面的内存类型(Normal/Device)和缓存策略(Write-Back/Write-Through/Non-Cacheable)。

AttrIndx 值 MAIR 属性 典型用途
0 Normal, Write-Back, Read-Allocate, Write-Allocate 普通内存(默认)
1 Device-nGnRnE 外设 MMIO 区域
2 Normal, Write-Through, No-Allocate 帧缓冲或特殊共享内存
3 Normal, Non-Cacheable DMA 一致性内存

4.4 软件自定义位(Linux 内核扩展)

ARM64 硬件规范保留了部分位供软件使用。Linux 内核利用这些位实现了额外的状态跟踪,这些位在硬件遍历时被忽略,但在内核缺页处理和页表操作中至关重要。

  • PTE_VALID(位[0]): 硬件有效位,内核直接使用。
  • PTE_DIRTY(软件位): 通常使用位[55]或[54](取决于配置),表示页面已被写入。
  • PTE_YOUNG(软件位): 通常使用位[56]或[53],表示页面最近被访问过(用于页面回收的 LRU 算法)。
  • PTE_SPECIAL(软件位): 用于标记零页、KSM 页面等特殊映射。
  • PTE_PROT_NONE(软件位): 用于 NUMA 迁移或用户fault页面,表示页面存在但无访问权限。
注意事项: 软件位的具体位置因内核版本和配置而异(如 CONFIG_ARM64_PA_BITS 影响高位可用空间)。在阅读内核源码时,应参考 arch/arm64/include/asm/pgtable.h 中的宏定义。

4.5 ARM64 PTE 位布局结构图

下图展示了 ARM64 4KB 页面 PTE 的完整 64 位布局,包括硬件定义字段和 Linux 内核常用的软件位区域:

ARM64 PTE 位布局图(4KB页面) ARM64 PTE 位布局(4KB页面,48位物理地址) 软件/保留 [63:52] PFN高 [51:48] 物理页帧号(PFN) [47:16] PFN低 [15:12] 属性 / 权限 / 标志 [11:0] 属性位详细展开 [11:0] V [0] PXN [1] UXN [2] Attr [4:3] NS [5] AP [7:6] SH [9:8] AF [10] nG [11] 图例: 物理地址 PFN主体 权限/标志 软件保留 属性索引 安全位 注:Linux 内核软件位(如 PTE_DIRTY、PTE_YOUNG)通常位于 [63:52] 软件保留区,具体位置依赖内核配置。

4.6 硬件遍历与 PTE 解析流程

当 MMU 进行页表遍历时,它按照以下步骤解析 PTE:

  1. 检查 Valid 位(位[0]): 若为0,立即触发 Translation Fault(缺页异常),停止遍历。
  2. 检查 AF 位(位[10]): 若为0,触发 Access Flag Fault,内核在异常处理中置位 AF 并返回。
  3. 解析 AttrIndx(位[4:3]): 读取 MAIR_EL1 寄存器,获取内存类型和缓存策略。
  4. 检查 AP 位(位[7:6]): 根据当前异常等级(EL0/EL1)和访问类型(读/写),判断是否触发 Permission Fault。
  5. 检查 UXN/PXN(位[2:1]): 若尝试执行被禁止的页面,触发 Instruction Abort。
  6. 提取物理地址: 将 PTE 中的 PFN 字段与虚拟地址的页内偏移拼接,形成最终的物理地址。
内核视角: 在缺页异常处理函数 do_page_fault() 中,内核通过读取 ESR_EL1(Exception Syndrome Register)获取故障类型(翻译故障、权限故障、访问标志故障等),然后根据 PTE 的软件位(如 PTE_DIRTY、PTE_YOUNG)决定是分配新页、置脏位还是进行写时复制(COW)。

4.7 典型配置示例

以下是一个用户进程匿名页的 PTE 典型值(假设硬件 DBM 使能):

// 用户态可读可写匿名页,已被访问和写入
// 物理地址 0x0000_0040_0000_0000,页内偏移忽略
// 属性:Normal Write-Back(AttrIndx=0),内部共享(SH=0b10)
// 权限:EL0 读/写(AP=0b01),UXN=1(用户不可执行),PXN=0
// AF=1,nG=1(非全局),Valid=1

PTE 二进制(高位到低位):
[63:52] 软件位: 0x000  (包含 PTE_DIRTY=1, PTE_YOUNG=1)
[51:48] PFN高:   0x0
[47:16] PFN主体: 0x0000_0040_0000
[15:12] PFN低:   0x0
[11]    nG:      1
[10]    AF:      1
[9:8]   SH:      0b10
[7:6]   AP:      0b01
[5]     NS:      0
[4:3]   Attr:    0b00
[2]     UXN:     1
[1]     PXN:     0
[0]     Valid:   1

// 十六进制表示(简化):
// 0x_0004_0000_0000_0C05
// 其中位[55](PTE_DIRTY)=1,位[56](PTE_YOUNG)=1
调试技巧: 在 Linux 内核中,可以使用 ptdump 或 /sys/kernel/debug/page_tables 导出当前进程的页表信息,直接查看 PTE 的原始十六进制值。结合上述位布局,可以快速诊断内存映射问题。