4. x86架构页表层级:32位PAE模式下的三级结构

在32位PAE(Physical Address Extension)模式下,x86架构将传统的二级页表扩展为三级页表结构,以支持最大64GB的物理内存寻址。PAE模式通过引入页目录指针表(PDPT)作为顶层,配合页目录(PD)和页表(PT),实现了对36位物理地址的映射。本节将深入剖析这三级的组成、索引方式及权限控制机制。

核心要点:PAE模式下的三级页表结构将线性地址拆分为4个字段:PDPT索引(2位)、PD索引(9位)、PT索引(9位)以及页内偏移(12位)。每个页表项(PTE)从32位扩展为64位,以容纳36位物理地址和扩展的权限标志。

4.1 三级页表结构总览

下图展示了32位PAE模式下线性地址到物理地址的完整转换路径。CPU通过CR3寄存器定位PDPT基址,经过三级查表最终获得物理页框号(PFN)。

32位PAE三级页表结构总览 32位PAE模式三级页表地址转换流程 线性地址(32位) PDPT索引(2位) PD索引(9位) PT索引(9位) 页内偏移(12位) CR3 → PDPT基址 页目录指针表 (PDPT) 4个条目 × 8字节 页目录 (PD) 512个条目 × 8字节 页表 (PT) 512个条目 × 8字节 物理 页框 关键参数说明: • 页大小:4KB(由12位页内偏移决定) • 物理地址宽度:36位(支持最大64GB物理内存) • 页表项大小:64位(8字节),相比非PAE模式的32位PTE翻倍 • 每个进程拥有独立的PDPT、PD和PT集合,由CR3切换实现上下文隔离

4.2 三级页表的索引与寻址机制

32位线性地址在PAE模式下被分解为4个字段,每个字段对应一级页表的索引或页内偏移。下表详细列出了各字段的位宽、作用及寻址范围。

字段名称 位范围 位宽 作用 寻址条目数
PDPT索引 [31:30] 2位 选择页目录指针表中的条目 4个PDPTE
PD索引 [29:21] 9位 选择页目录中的条目 512个PDE
PT索引 [20:12] 9位 选择页表中的条目 512个PTE
页内偏移 [11:0] 12位 定位物理页内的字节 4096字节
寻址能力计算:4(PDPT条目)× 512(PD条目)× 512(PT条目)× 4KB(页大小)= 4GB线性地址空间。PAE模式并未扩大线性地址空间(仍为32位),但通过64位PTE扩展了物理地址宽度。

4.3 页目录指针表(PDPT)结构

PDPT是PAE模式引入的顶层页表结构,位于物理内存中,其基址由CR3寄存器的高27位指定(32位模式下CR3的低5位被忽略,因为PDPT必须32字节对齐)。PDPT包含4个64位的条目(PDPTE),每个条目指向一个页目录(PD)。

PDPT结构及CR3关联 页目录指针表(PDPT)结构 CR3寄存器(32位) [31:5] PDPT基址 | [4:0] 忽略 页目录指针表(PDPT) PDPTE[0](索引00b)→ PD基址A PDPTE[1](索引01b)→ PD基址B PDPTE[2](索引10b)→ PD基址C PDPTE[3](索引11b)→ PD基址D PDPTE格式(64位): 位[63:36]:保留(必须为0) 位[35:12]:PD物理基址(24位) 位[11:1]:保留/属性 位[0]:存在位(P)

4.4 页目录(PD)与页表(PT)结构

页目录(PD)和页表(PT)在PAE模式下均包含512个64位条目。PD中的每个条目(PDE)指向一个页表(PT),而PT中的每个条目(PTE)指向一个4KB物理页框。两者的条目格式高度相似,但PDE支持2MB大页映射(当PS位=1时)。

条目类型 条目数 条目大小 映射粒度 特殊标志位
PDE(页目录条目) 512 8字节 4KB(正常)或 2MB(大页) PS位(位7):0=4KB页,1=2MB大页
PTE(页表条目) 512 8字节 4KB PAT位(位7):页属性表索引
大页映射注意事项:当PDE的PS位为1时,PDE直接映射2MB物理内存,此时不再查询PT。PDE的[31:21]位提供2MB页框的高位地址,线性地址的[20:0]作为页内偏移。大页映射可减少TLB压力,但要求物理内存连续2MB对齐。

4.5 64位PTE的权限控制字段

PAE模式下,每个PTE扩展为64位,新增了多个权限控制位。下表列出了关键权限字段及其作用。

位域 名称 作用描述
位0 P(Present) 存在位:1=页在物理内存中,0=页不在(触发缺页异常)
位1 R/W(Read/Write) 读写权限:0=只读,1=可读写
位2 U/S(User/Supervisor) 特权级:0=仅内核态(CPL<3)可访问,1=用户态(CPL=3)也可访问
位3 PWT(Page Write-Through) 页级写透缓存策略
位4 PCD(Page Cache Disable) 页级缓存禁用
位5 A(Accessed) 访问位:由CPU硬件置1,表示页被访问过
位6 D(Dirty) 脏位:由CPU硬件置1,表示页被写入过(仅PTE有效)
位7 PS/PAT PDE中为大页标志;PTE中为PAT索引
位8 G(Global) 全局页:置1时TLB刷新时不被清除(需CR4.PGE=1)
位[11:9] AVL(Available) 软件可用位:操作系统可自定义用途
位[35:12] PFN(Page Frame Number) 物理页框号:对应36位物理地址的高24位
位[63:36] Reserved 保留位:必须为0,否则引发#GP异常

4.6 地址转换完整流程

以下步骤描述了CPU在PAE模式下如何将线性地址转换为物理地址:

  1. 读取CR3:CPU从CR3寄存器获取PDPT的物理基址(32字节对齐)。
  2. 查询PDPT:使用线性地址[31:30]作为索引,从PDPT中读取对应的PDPTE。检查PDPTE的P位,若为0则触发缺页异常。
  3. 查询PD:从PDPTE中提取PD基址(位[35:12]),使用线性地址[29:21]作为索引读取PDE。检查PDE的P位和PS位:
    • 若PS=0:继续步骤4。
    • 若PS=1:PDE直接映射2MB大页,物理地址 = (PDE[31:21] << 21) | 线性地址[20:0]。
  4. 查询PT:从PDE中提取PT基址(位[35:12]),使用线性地址[20:12]作为索引读取PTE。检查PTE的P位。
  5. 组合物理地址:物理地址 = (PTE[35:12] << 12) | 线性地址[11:0]。
  6. 权限检查:CPU根据PTE的R/W、U/S位以及当前CPL进行访问权限校验,不匹配则触发#GP异常。
PAE地址转换完整流程图 PAE模式地址转换完整流程图 开始地址转换 ① 读取CR3 → 获取PDPT基址 ② 线性地址[31:30] → 查询PDPTE ③ 线性地址[29:21] → 查询PDE PS=1? 是 2MB大页映射 物理地址=PDE[31:21]<<21 | 偏移 否 ④ 查询PT 线性地址[20:12]→PTE

4.7 权限控制与保护机制

PAE模式下的权限检查在每一级页表查询时都会执行,但最终的有效权限由PDE和PTE的权限字段取交集决定。以下是权限组合规则:

  • U/S位:若PDE或PTE中任一U/S=0,则该页为超级用户页(仅内核可访问)。只有两级U/S均为1时,用户态才可访问。
  • R/W位:若PDE或PTE中任一R/W=0,则该页为只读页。只有两级R/W均为1时,页才可写。
  • NX位(执行禁用):在支持XD/NX的CPU上,PTE的位63作为执行禁用位。置1时该页不可执行代码,用于防止缓冲区溢出攻击。
实践建议:在配置PAE页表时,建议在PDE级别设置较宽松的权限(如U/S=1, R/W=1),而在PTE级别进行精细控制。这样可以在不频繁修改PD的情况下,灵活调整单个页面的权限。例如,代码段可在PDE设R/W=0,PTE设U/S=1;数据段在PDE设R/W=1,PTE根据需求设置。

4.8 典型配置示例

以下代码片段展示了如何在C语言中定义PAE页表项结构体,并初始化一个简单的页表映射:

/* PAE模式页表项结构定义(64位) */
typedef struct {
    uint64_t present    : 1;  /* 位0: 存在位 */
    uint64_t rw         : 1;  /* 位1: 读写 */
    uint64_t us         : 1;  /* 位2: 用户/超级用户 */
    uint64_t pwt        : 1;  /* 位3: 写透 */
    uint64_t pcd        : 1;  /* 位4: 缓存禁用 */
    uint64_t accessed   : 1;  /* 位5: 访问位 */
    uint64_t dirty      : 1;  /* 位6: 脏位(仅PTE) */
    uint64_t ps_or_pat  : 1;  /* 位7: PDE大页/PTE PAT */
    uint64_t global     : 1;  /* 位8: 全局页 */
    uint64_t avail      : 3;  /* 位[11:9]: 软件可用 */
    uint64_t pfn        : 24; /* 位[35:12]: 物理页框号 */
    uint64_t reserved   : 28; /* 位[63:36]: 保留 */
} __attribute__((packed)) pae_pte_t;

/* 初始化一个用户态可读写页表条目 */
void init_user_rw_pte(pae_pte_t *pte, uint64_t phys_addr) {
    pte->present = 1;
    pte->rw      = 1;  /* 可读写 */
    pte->us      = 1;  /* 用户态可访问 */
    pte->pwt     = 0;
    pte->pcd     = 0;
    pte->accessed= 0;
    pte->dirty   = 0;
    pte->ps_or_pat=0;
    pte->global  = 0;
    pte->avail   = 0;
    pte->pfn     = (phys_addr >> 12) & 0xFFFFFF;
    pte->reserved= 0;
}
调试技巧:在QEMU中启用PAE模式时,可使用 info tlb 命令查看当前TLB缓存,或使用 info mem 查看页表结构。若遇到#GP异常,检查保留位(位[63:36])是否被错误置1,这是PAE模式下最常见的配置错误。