Linux 页表机制详解(x86_64 架构)

前言

在 AI 计算和推理加速的内核驱动开发中,高效的内存管理是构建高性能计算系统的核心基础。无论是 GPU 显存映射、DMA 缓冲区管理,还是大规模张量数据的虚拟地址分配,都离不开对页表机制的深入理解。本文系统梳理 x86_64 架构下 Linux 的四级页表机制,为 AI 计算相关的内核驱动开发提供理论基础和实践参考。

1. 概述

在 x86_64 架构的 Linux 系统中,页表采用四级分页模型来实现虚拟地址到物理地址的转换。虽然现代硬件支持五级页表,但 Linux 默认启用四级分页,以多级页表项的形式分层存储在物理内存中。

2. 四级页表结构

Linux 对 x86_64 的虚拟地址(默认 48 位有效地址)进行分段,依次索引四级页表, 页表层级说明如下:

页表层级全称索引位宽虚拟地址位段作用
PGD页全局目录 (Page Global Directory)9 位47~39索引 PGD 项,指向 PUD 页的物理地址
PUD页上级目录 (Page Upper Directory)9 位38~30索引 PUD 项,指向 PMD 页的物理地址
PMD页中间目录 (Page Middle Directory)9 位29~21索引 PMD 项,指向 PT 页或 2MB 大页面
PT页表 (Page Table)9 位20~12索引 PT 项,指向 4KB 物理页面
页内偏移-12 位11~0物理页面内的字节偏移

2.1. 各级页表详解

  1. PGD(页全局目录)

    • 进程的 mm_struct 中保存 PGD 物理地址
    • 虚拟地址最高 9 位索引 PGD 项
    • 指向 PUD 页的物理地址
  2. PUD(页上级目录)

    • 中间层级,9 位索引 PUD 项
    • 指向 PMD 页物理地址
    • 在 4KB 页面且地址范围较小时常与 PGD 合并
  3. PMD(页中间目录)

    • 9 位索引 PMD 项
    • 可指向 PT 页物理地址
    • 或直接映射 2MB 大页面(此时跳过 PT 层级)
  4. PT(页表)

    • 最低层级,9 位索引 PT 项
    • 直接指向 4KB 物理页面的起始地址
    • 虚拟地址最低 12 位为页内偏移量

3. 页表项(PTE)存储格式

每个页表项占 8 字节(64 位),包含两部分核心信息:

页表项(64 位 / 8 字节)结构:
┌────────────────────────────────────────────────────────────────┐
│ 63                                                           0 │
├──────────────────────────────────────────┬─────────────────────┤
│    物理页面基地址 (52 位)                   │  标志位 (12 位)      │
│    [63:12]                               │  [11:0]             │
└──────────────────────────────────────────┴─────────────────────┘

详细位段分布:
┌──┬──┬──┬──┬──────────────────────────────────┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│N │PK│PK│PK│                                  │  │P │  │  │P │P │U │R │  │  │  │P │
│X │3 │2 │1 │      物理页面基地址                │G │A │D │A │C │W │/ │/ │  │  │  │  │
│  │  │  │  │  (Physical Page Base Address)    │  │T │  │  │D │T │S │W │  │  │  │  │
│  │  │  │  │                                  │  │  │  │  │  │  │  │  │  │  │  │  │
├──┼──┼──┼──┼──────────────────────────────────┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
│63│62│61│60│59                              12│11│10│ 9│ 8│ 7│ 6│ 5│ 4│ 3│ 2│ 1│ 0│
└──┴──┴──┴──┴──────────────────────────────────┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘

3.1. 物理地址部分(位 63-12)

  • 高 52 位:目标物理页的基地址
  • 物理地址仅低 52 位有效,由 CPU 架构决定
  • 由于页面按 4KB 对齐,低 12 位始终为 0,因此不需要存储

3.2. 权限与状态标志位(位 11-0)

标志位名称作用
0P存在位 (Present)标记页是否在物理内存中
1R/W读写权限 (Read/Write)0=只读,1=可读写
2U/S用户/内核空间 (User/Supervisor)0=仅内核态,1=用户态可访问
3PWT写通 (Page Write-Through)控制写缓存策略
4PCD禁用缓存 (Page Cache Disable)1=禁用页面缓存
5A访问位 (Accessed)标记页是否被访问过
6D脏位 (Dirty)标记页是否被修改过
7PAT页面属性 (Page Attribute Table)与 PWT/PCD 配合控制缓存
8G全局位 (Global)1=全局页,TLB 刷新时保留
9-11AVL可用位 (Available)操作系统自定义使用
63NX禁止执行 (No eXecute)1=禁止从该页执行代码

4. 页表空间占用

在 x86_64 架构的 Linux 四级分页模型中,每级页表默认占用 4KB 物理内存

4.1. 空间计算逻辑

  • 每个页表项(PTE/PMD/PUD/PGD 项)占 8 字节
  • 一个 4KB 页面可容纳:4096 ÷ 8 = 512 个页表项
  • 虚拟地址中每级页表的索引位宽为 9 位, 2^9 = 512,刚好匹配一个 4KB 页面能存储的页表项数量

因此,PGD、PUD、PMD、PT 各级页表只要独立存在,单级占用空间都是 4KB

4.2. 特殊情况

  1. 2MB 大页面

    • PMD 项直接映射物理大页
    • 跳过 PT 层级,节省 4KB 的 PT 页空间
  2. 1GB 超大页面

    • PUD 项直接映射物理超大页
    • 跳过 PMD 和 PT 层级,节省两级页表空间
  3. 地址范围较小时

    • PUD 层会与 PGD 层合并
    • PUD 无需单独占用 4KB 空间

5. 内核与用户页表隔离

5.1. 用户进程页表

  • 每个进程有独立的 PGD
  • 用户空间(0x0000000000000000 ~ 0x00007FFFFFFFFFFF)的页表项由进程私有
  • 进程切换时只需切换 CR3 寄存器指向新进程的 PGD 物理地址

5.2. 内核页表

  • 内核空间(0xFFFF800000000000 及以上)的页表项在所有进程的 PGD/PUD/PMD 中共享
  • 实现内核地址空间的全局映射
  • 避免每次进程切换时重新映射内核空间

6. 硬件关联与管理

6.1. 硬件支持

  • CR3 寄存器:存储当前进程 PGD 的物理地址
  • CPU 地址转换:自动遍历四级页表完成虚拟地址到物理地址的转换
  • TLB(Translation Lookaside Buffer):缓存虚拟地址到物理地址的映射,减少页表遍历开销

6.2. 内核管理

6.2.1. 关键数据结构
/* 进程内存描述符 - 定义在 include/linux/mm_types.h */
struct mm_struct {
    pgd_t *pgd;                    /* 指向进程的页全局目录(页表根) */
    /* ... 更多字段 ... */
};

/* 页表项类型定义 - 定义在 arch/x86/include/asm/pgtable_types.h */
//所有的页表项都是8字节的一个定义
typedef struct { unsigned long pgd; } pgd_t;    /* 页全局目录项 */
typedef struct { unsigned long pud; } pud_t;    /* 页上级目录项 */
typedef struct { unsigned long pmd; } pmd_t;    /* 页中间目录项 */
typedef struct { unsigned long pte; } pte_t;    /* 页表项 */

6.2.2. 关键管理函数

页表分配函数

/* 分配各级页表 */
pgd_t *pgd_alloc(struct mm_struct *mm);          /* 分配页全局目录 */
int __pud_alloc(struct mm_struct *mm,
         pgd_t *pgd, unsigned long address);
int __pmd_alloc(struct mm_struct *mm,
         pud_t *pud, unsigned long address);
int __pte_alloc(struct mm_struct *mm, pmd_t *pmd);

/* 页面分配 */
struct page *alloc_pages(gfp_t gfp_mask,
                    unsigned int order);
void __free_pages(struct page *page, unsigned int order);

页表遍历与查找函数

/* 根据虚拟地址查找各级页表项 */
pgd_t *pgd_offset(struct mm_struct *mm, unsigned long address);
pud_t *pud_offset(pgd_t *pgd, unsigned long address);
pmd_t *pmd_offset(pud_t *pud, unsigned long address);
pte_t *pte_offset_map(pmd_t *pmd, unsigned long address);

/* 页表项状态检查 */
int pte_present(pte_t pte);     /* 检查页是否在内存中 */
int pte_young(pte_t pte);       /* 检查访问位 */
int pte_dirty(pte_t pte);       /* 检查脏位 */
int pte_write(pte_t pte);       /* 检查写权限 */

页表项修改函数

/* 设置页表项 */
void set_pte(pte_t *ptep, pte_t pte);
void set_pmd(pmd_t *pmdp, pmd_t pmd);
void set_pud(pud_t *pudp, pud_t pud);
void set_pgd(pgd_t *pgdp, pgd_t pgd);

/* 页表项属性修改 */
pte_t pte_mkwrite(pte_t pte);   /* 设置可写 */
pte_t pte_wrprotect(pte_t pte); /* 设置写保护 */
pte_t pte_mkdirty(pte_t pte);   /* 设置脏位 */
pte_t pte_mkyoung(pte_t pte);   /* 设置访问位 */

页表释放函数

/* 释放各级页表 */
void pgd_free(struct mm_struct *mm, pgd_t *pgd);
void pud_free(struct mm_struct *mm, pud_t *pud);
void pmd_free(struct mm_struct *mm, pmd_t *pmd);
void pte_free(struct mm_struct *mm, pgtable_t pte);

TLB 管理函数

/* TLB 刷新操作 */
/* 刷新所有 CPU 的 TLB */
void flush_tlb_all(void);
/* 刷新指定进程的 TLB */
void flush_tlb_mm(struct mm_struct *mm);
void flush_tlb_page(struct vm_area_struct *vma,
             unsigned long addr);
void flush_tlb_range(struct vm_area_struct *vma, 
             unsigned long start, unsigned long end);

7. 地址转换实例

7.1. 示例参数设定

假设需要转换虚拟地址:0x0000 1234 5678 ABCD

已知条件:

  • 进程 PGD 物理地址:0x100000
  • 页表项标志位:P=1(存在)、RW=1(可读写)
  • 高 52 位为物理地址基址

7.2. 步骤 1:拆分虚拟地址位段

将 48 位虚拟地址 0x000012345678ABCD 按位段拆分:

虚拟地址:0x0000 1234 5678 ABCD
         ↓     ↓    ↓    ↓
二进制:  0000 0000 0000 0001 0010 0011 0100 0101 0110 0111 1000 1010 1011 1100 1101

位段拆分:
├─ PGD 索引 [47:39]:0x000 (十进制: 0)
├─ PUD 索引 [38:30]:0x024 (十进制: 36)
├─ PMD 索引 [29:21]:0x08D (十进制: 141)
├─ PT 索引  [20:12]:0x171 (十进制: 369)
└─ 页内偏移 [11:0] :0xBCD (十进制: 3021)

7.3. 步骤 2:四级页表逐级寻址

第一级:寻址 PGD 项
PGD 物理地址 = 0x100000
PGD 索引 = 0x000
PGD 项物理地址 = 0x100000 + (0x000 × 8) = 0x100000

假设 PGD 项值 = 0x110000_000000067
└─ 高 52 位 0x110000 → PUD 页的物理地址
第二级:寻址 PUD 项
PUD 页物理地址 = 0x110000
PUD 索引 = 0x024
PUD 项物理地址 = 0x110000 + (0x024 × 8) = 0x110120

假设 PUD 项值 = 0x120000_000000067
└─ 高 52 位 0x120000 → PMD 页的物理地址
第三级:寻址 PMD 项
PMD 页物理地址 = 0x120000
PMD 索引 = 0x08D
PMD 项物理地址 = 0x120000 + (0x08D × 8) = 0x120438

假设 PMD 项值 = 0x130000_000000067
└─ 高 52 位 0x130000 → PT 页的物理地址
第四级:寻址 PT 项
PT 页物理地址 = 0x130000
PT 索引 = 0x171
PT 项物理地址 = 0x130000 + (0x171 × 8) = 0x130888

假设 PT 项值 = 0x140000_000000067
└─ 高 52 位 0x140000 → 最终物理页面的基地址

7.4. 步骤 3:计算最终物理地址

物理地址 = 物理页面基地址 + 页内偏移
        = 0x140000 + 0xBCD
        = 0x140BCD

7.5. 转换流程图

虚拟地址: 0x000012345678ABCD
    ↓
┌─────────────────────────────────────┐
│ CR3 寄存器: 0x100000 (PGD 基址)       │
└─────────────────────────────────────┘
    ↓ [PGD 索引: 0x000]
┌─────────────────────────────────────┐
│ PGD[0] = 0x110000_000000067         │
│ → PUD 基址: 0x110000                 │
└─────────────────────────────────────┘
    ↓ [PUD 索引: 0x024]
┌─────────────────────────────────────┐
│ PUD[36] = 0x120000_000000067        │
│ → PMD 基址: 0x120000                 │
└─────────────────────────────────────┘
    ↓ [PMD 索引: 0x08D]
┌─────────────────────────────────────┐
│ PMD[141] = 0x130000_000000067       │
│ → PT 基址: 0x130000                  │
└─────────────────────────────────────┘
    ↓ [PT 索引: 0x171]
┌─────────────────────────────────────┐
│ PT[369] = 0x140000_000000067        │
│ → 物理页基址: 0x140000                │
└─────────────────────────────────────┘
    ↓ [页内偏移: 0xBCD]
┌─────────────────────────────────────┐
│ 最终物理地址: 0x140BCD                │
└─────────────────────────────────────┘

8. 性能优化机制

8.1. TLB 缓存

  • TLB 是 CPU 内部的高速缓存,存储最近使用的虚拟地址到物理地址的映射
  • 命中 TLB 时无需遍历四级页表,大幅提升地址转换速度
  • TLB 失效时才触发页表遍历(Page Table Walk)

8.2. 大页面支持

  • 2MB 大页面:减少页表层级,降低 TLB 压力
  • 1GB 超大页面:适用于大内存应用,进一步减少页表开销
  • 通过 hugetlbfs 文件系统或 mmapMAP_HUGETLB 标志使用

8.3. 页表缓存

  • 内核维护页表缓存池,避免频繁分配和释放页表页
  • 使用 slab 分配器管理页表项

9. 总结

Linux 的四级页表机制通过分层索引的方式,实现了灵活、高效的虚拟地址到物理地址的转换。整个过程由 CPU 硬件自动完成,配合 TLB 缓存和大页面支持,在保证内存隔离和保护的同时,提供了出色的性能表现。

关键要点:

  1. 四级页表结构:PGD → PUD → PMD → PT → 物理页
  2. 每级页表占用 4KB 物理内存,每个页表项 8 字节
  3. 48 位虚拟地址分段索引,12 位页内偏移
  4. CR3 寄存器存储 PGD 基址,TLB 缓存加速转换
  5. 支持大页面优化,实现进程隔离和内存保护

这套机制是现代操作系统内存管理的基石,为应用程序提供了统一、安全、高效的内存访问接口。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值