3. 页表与页表项:页表的基本结构,一级页表与二级页表,页表项(PTE)的格式与关键标志位(XN, AP, Domain等)
页表是MMU实现虚拟地址到物理地址转换的核心数据结构。ARM架构支持多级页表结构,其中一级页表(L1)和二级页表(L2)是最常见的配置。页表中的每个条目称为页表项(PTE),它包含了地址映射信息和内存访问控制属性。
3.1 页表的基本结构
ARM的页表采用多级层次结构,以平衡地址空间覆盖范围和页表占用内存之间的矛盾。一级页表将4GB虚拟地址空间划分为多个粗粒度区域,二级页表则对一级页表指向的区域进行细粒度划分。
核心概念: 一级页表提供“目录”功能,二级页表提供“页面”功能。这种两级结构使得系统可以只为实际使用的虚拟地址区域分配二级页表,从而节省大量内存。
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)格式
3.3.2 二级页表项(L2 PTE)格式
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 =