3. 页表与页表项:页表的基本结构,一级页表与二级页表,页表项(PTE)的格式与关键标志位(XN, AP, Domain等)

页表是MMU实现虚拟地址到物理地址转换的核心数据结构。ARM架构支持多级页表结构,其中一级页表(L1)和二级页表(L2)是最常见的配置。页表中的每个条目称为页表项(PTE),它包含了地址映射信息和内存访问控制属性。

3.1 页表的基本结构

ARM的页表采用多级层次结构,以平衡地址空间覆盖范围和页表占用内存之间的矛盾。一级页表将4GB虚拟地址空间划分为多个粗粒度区域,二级页表则对一级页表指向的区域进行细粒度划分。

核心概念: 一级页表提供“目录”功能,二级页表提供“页面”功能。这种两级结构使得系统可以只为实际使用的虚拟地址区域分配二级页表,从而节省大量内存。
两级页表地址转换结构 虚拟地址 L1索引 L2索引 页内偏移 L1基址 + 索引×4 一级页表 (L1) 4096个条目 每个条目4字节 覆盖4GB空间 L1 PTE指向 二级页表 (L2) 256个条目 每个条目4字节 覆盖1MB空间 L2 PTE指向 物理页 4KB (小页) 转换过程: 1. MMU从TTBRx寄存器获取L1页表基址 → 2. 用虚拟地址L1索引查找L1页表项 → 3. L1 PTE指向L2页表基址 → 4. 用L2索引查找L2页表项 → 5. L2 PTE包含物理页基址 → 6. 物理基址 + 页内偏移 = 最终物理地址

3.2 一级页表与二级页表

3.2.1 一级页表(L1)

一级页表位于内存中,其基地址由系统控制协处理器CP15的TTBRx(Translation Table Base Register)寄存器指定。L1页表包含4096个条目,每个条目4字节,总大小16KB。虚拟地址的高12位(bits[31:20])作为L1索引。

属性 值 说明
条目数量 4096 覆盖4GB地址空间(每个条目对应1MB)
条目大小 4字节 32位页表项
页表总大小 16KB 4096 × 4字节
索引位 VA[31:20] 12位索引
粒度 1MB(段映射) L1可直接映射1MB段

3.2.2 二级页表(L2)

当一级页表项指向二级页表时,二级页表对1MB区域进行细粒度划分。L2页表包含256个条目,每个条目4字节,总大小1KB。虚拟地址的中间8位(bits[19:12])作为L2索引。

属性 值 说明
条目数量 256 覆盖1MB地址空间
条目大小 4字节 32位页表项
页表总大小 1KB 256 × 4字节
索引位 VA[19:12] 8位索引
支持页面类型 大页(64KB)、小页(4KB) 细粒度映射
设计权衡: 一级页表直接映射(段映射)适合大块连续内存区域,如内核空间;二级页表映射适合需要细粒度控制的用户空间,支持按页(4KB)进行权限和属性控制。

3.3 页表项(PTE)的格式

页表项是一个32位的描述符,包含了物理地址基址和内存访问控制属性。一级页表项和二级页表项的格式略有不同,但都包含关键的标志位。

3.3.1 一级页表项(L1 PTE)格式

一级页表项(L1 PTE)位域格式 物理基址[31:20] SBZ Domain XN C B AP TEX APX S nG 0 类型 31:20 19:16 15:12 11 10 9 8:7 6 5 4 3 2 1:0 L1 PTE类型(bits[1:0]): 00 = 无效(fault) | 01 = 指向L2页表(page table) | 10 = 段映射(section, 1MB) | 11 = 超级段映射(supersection, 16MB)

3.3.2 二级页表项(L2 PTE)格式

二级页表项(L2 PTE)位域格式(小页4KB) 物理基址[31:12] SBZ XN C B AP TEX APX S nG 0 类型 31:12 11:9 8 7 6 5:4 3 2 1 0 L2 PTE类型(bits[1:0]): 00 = 无效(fault) | 01 = 大页(large page, 64KB) | 10 = 小页(small page, 4KB) | 11 = 极小页(tiny page, 1KB,仅ARMv5及之前)

3.4 关键标志位详解

页表项中的标志位控制着内存访问的权限、缓存策略和执行属性。以下是ARM架构中最重要的几个标志位:

标志位 全称 位位置 功能描述
XN eXecute Never L1: bit[11], L2: bit[8] 禁止执行标志。置1时,该内存区域不允许执行指令,用于防止代码注入攻击(如栈溢出攻击)。
AP Access Permission L1: bits[8:7], L2: bits[5:4] 访问权限位。与APX位配合,定义用户模式和特权模式的读写权限。
Domain Domain L1: bits[15:12] 域标识符(仅L1)。将内存区域划分为最多16个域,通过CP15的域访问控制寄存器(DACR)进行粗粒度权限控制。
C/B Cacheable / Bufferable C: bit[10], B: bit[9] (L1) 缓存和写缓冲控制。C=1启用数据缓存,B=1启用写缓冲,组合定义内存类型(强序、设备、正常)。
TEX Type Extension L1: bit[6], L2: bit[3] 类型扩展位。与C/B位共同编码内存属性,用于ARMv6/v7的扩展内存类型和共享属性。
APX Access Permission eXtension L1: bit[5], L2: bit[2] 访问权限扩展位。与AP位组合,提供更精细的权限控制,支持只读、读写、只执行等模式。
nG not Global L1: bit[3], L2: bit[0] 非全局标志。nG=1表示该页表项是进程私有的,TLB缓存需要ASID(地址空间标识符)匹配;nG=0表示全局映射(如内核空间),所有进程共享。
S Shareable L1: bit[4], L2: bit[1] 共享属性。S=1表示该内存区域在多个处理器之间共享,需要硬件维护缓存一致性。

3.4.1 AP与APX组合权限控制

AP和APX两位组合定义了四种访问权限模式:

APX AP[1:0] 特权模式访问 用户模式访问 典型用途
0 00 读写 无访问 内核私有数据
0 01 读写 读写 用户共享数据
0 10 读写 只读 用户只读数据(如代码段)
0 11 读写 无访问 同00,保留
1 00 只读 无访问 内核只读数据
1 01 只读 只读 全局只读数据

3.4.2 Domain域控制

Domain字段是L1页表项独有的标志位,提供了一种粗粒度的内存访问控制机制。CP15的域访问控制寄存器(DACR)为每个域(共16个)分配2位控制码:

DACR编码 控制模式 行为描述
00 No Access 任何对该域内页面的访问都会触发域故障(Domain Fault)
01 Client 访问权限由页表项中的AP位决定(最常用模式)
10 Reserved 保留,不可用
11 Manager 不检查AP权限,所有访问允许(用于内核关键区域)
安全注意事项: XN位是ARM架构中实现NX(No-Execute)保护的关键机制。在嵌入式系统中,务必为数据段(如栈、堆)设置XN=1,防止攻击者将数据区作为代码执行。Domain机制在ARMv7-A中被保留,但在ARMv8-A中被更灵活的Translation Regime取代。

3.5 页表配置代码示例

以下代码展示了如何在ARM裸机环境中初始化一级页表并配置页表项:

/* 一级页表基址,要求16KB对齐 */
#define L1_PAGE_TABLE_BASE  0x40000000
/* 页表项类型定义 */
#define L1_TYPE_FAULT       0x00000000
#define L1_TYPE_PAGE_TABLE  0x00000001
#define L1_TYPE_SECTION     0x00000002
/* 标志位定义 */
#define L1_SECTION_XN       (1 << 11)   /* 禁止执行 */
#define L1_SECTION_DOMAIN(x) ((x) << 12) /* 域编号 */
#define L1_SECTION_AP_RW    (0x1 << 7)  /* 特权读写,用户无访问 */
#define L1_SECTION_AP_RWRW  (0x3 << 7)  /* 特权读写,用户读写 */
#define L1_SECTION_C        (1 << 10)   /* 可缓存 */
#define L1_SECTION_B        (1 << 9)    /* 可缓冲 */
#define L1_SECTION_NG       (1 << 3)    /* 非全局 */
/* 初始化一级页表 */
void init_page_table(void)
{
    uint32_t *l1_table = (uint32_t *)L1_PAGE_TABLE_BASE;
    uint32_t va, pa;
    int i;
    /* 1. 将所有L1条目初始化为fault(无效) */
    for (i = 0; i < 4096; i++) {
        l1_table[i] = L1_TYPE_FAULT;
    }
    /* 2. 映射内核代码段:0x80000000 -> 0x80000000, 1MB段映射 */
    /*    属性:特权读写,可缓存,域0,可执行 */
    va = 0x80000000;
    pa = 0x80000000;
    l1_table[va >> 20] = (pa & 0xFFF00000) |
                          L1_TYPE_SECTION |
                          L1_SECTION_DOMAIN(0) |
                          L1_SECTION_AP_RW |
                          L1_SECTION_C |
                          L1_SECTION_B;
    /* 3. 映射外设区域:0xE0000000 -> 0xE0000000, 1MB段映射 */
    /*    属性:特权读写,不可缓存,域1,禁止执行(XN) */
    va = 0xE0000000;
    pa =