4. 分页机制原理:页框、页、页表与页表项
分页(Paging)是现代操作系统内存管理的核心机制,它通过将虚拟地址空间和物理内存划分为固定大小的块,实现了非连续物理内存分配、进程隔离与虚拟内存扩展。理解分页机制,必须首先掌握四个基本概念:页框(Page Frame)、页(Page)、页表(Page Table)和页表项(PTE)。
核心要点:分页机制的本质是建立一张从虚拟页号到物理页框号的映射表(页表),由 MMU 硬件在每次内存访问时自动完成地址转换。页框是物理内存的划分单位,页是虚拟地址空间的划分单位,页表存储映射关系,页表项是映射表中的单条记录。
4.1 页框(Page Frame)—— 物理内存的等分单位
物理内存被划分为固定大小的连续区域,每个区域称为一个页框(也称物理页、帧)。页框是物理内存分配和回收的最小单位。
- 大小固定:通常为 4KB(x86/ARM 标准页),也支持 2MB/1GB 大页。
- 物理连续:页框内部的物理地址是连续的,但不同页框之间可以不连续。
- 编号管理:每个页框有一个唯一的物理页框号(PFN,Page Frame Number),物理地址 = PFN × 页框大小 + 页内偏移。
- 内核管理:Linux 内核通过
struct page结构体数组(mem_map)管理所有页框的状态(空闲、使用中、脏页、锁定等)。
| 属性 | 说明 | 典型值 |
|---|---|---|
| 大小 | 固定长度,由硬件架构决定 | 4KB / 16KB / 64KB |
| 物理连续性 | 页框内部连续,页框间不连续 | — |
| 标识 | 物理页框号(PFN) | 0 ~ (总物理内存/页框大小 - 1) |
| 内核数据结构 | struct page(约 64 字节) | 每个页框对应一个 |
| 分配接口 | alloc_pages() / __get_free_pages() | 返回页框的虚拟地址或 struct page 指针 |
关键区别:页框是物理概念,存在于物理内存中;页是虚拟概念,存在于进程的虚拟地址空间中。一个页框可以映射到多个进程的虚拟页(共享内存),但一个虚拟页在同一时刻只能映射到一个页框(除非使用别名的特殊映射)。
4.2 页(Page)—— 虚拟地址空间的等分单位
进程的虚拟地址空间被划分为与页框大小相同的连续区域,每个区域称为一个页(也称虚拟页)。页是虚拟内存分配和管理的最小单位。
- 大小与页框一致:虚拟页大小必须等于物理页框大小,否则 MMU 无法完成地址转换。
- 虚拟连续:虚拟地址空间中的页在逻辑上是连续的,但映射到的物理页框可以分散在物理内存各处。
- 编号管理:每个虚拟页有一个虚拟页号(VPN,Virtual Page Number),虚拟地址 = VPN × 页大小 + 页内偏移。
- 状态属性:每个虚拟页有读/写/执行权限、是否在物理内存中(存在位)、是否被访问/修改(A/D 位)等属性。
// 虚拟地址分解示例(4KB 页,32 位地址空间)
// 虚拟地址:0x12345678
// 页大小:4KB = 2^12,页内偏移占 12 位
// VPN 占 20 位,页内偏移占 12 位
VPN = 0x12345 // 虚拟页号
offset = 0x678 // 页内偏移
// 物理地址 = PFN × 4096 + offset
// 假设 PFN = 0xABCDE
// 物理地址 = 0xABCDE000 + 0x678 = 0xABCDE678
4.3 页表(Page Table)—— 地址映射的目录结构
页表是存储在物理内存中的数据结构,用于将虚拟页号(VPN)映射到物理页框号(PFN)。每个进程拥有独立的页表,从而实现进程间的地址空间隔离。
页表的结构随硬件架构演变,从单级页表发展为多级页表,以节省内存占用:
- 单级页表:一个线性数组,VPN 直接作为索引。32 位系统(4GB 虚拟空间,4KB 页)需要 2^20 ≈ 1M 个页表项,每个进程占用 4MB 内存,浪费严重。
- 多级页表:将 VPN 拆分为多个索引,逐级查找。x86_64 使用 4 级页表(PGD → PUD → PMD → PTE),ARMv8 使用 3~4 级。未使用的中间级页表可以不分配,大幅节省内存。
- 页表基址寄存器:CPU 的 CR3 寄存器(x86)或 TTBR0_EL1(ARM)保存当前进程页表的物理基地址,进程切换时更新该寄存器。
性能陷阱:多级页表虽然节省内存,但每次地址转换需要多次内存访问(4 级页表需要 4 次查表)。TLB(Translation Lookaside Buffer)作为页表的高速缓存,是缓解这一性能瓶颈的关键硬件组件。
4.4 页表项(PTE)—— 映射记录的基本单元
页表项(Page Table Entry, PTE)是页表中的单条记录,包含虚拟页到物理页框的映射信息以及控制权限。每个 PTE 的大小通常为 8 字节(64 位架构)或 4 字节(32 位架构)。
| 位域 | 名称 | 说明 |
|---|---|---|
| Bit 0 | Present (P) | 存在位:1 表示该页在物理内存中,0 表示缺页 |
| Bit 1 | Read/Write (R/W) | 读写位:0 表示只读,1 表示可读写 |
| Bit 2 | User/Supervisor (U/S) | 权限位:0 表示仅内核可访问,1 表示用户态也可访问 |
| Bit 3 | Page Write Through (PWT) | 写直达缓存策略控制 |
| Bit 4 | Page Cache Disable (PCD) | 禁止该页被缓存 |
| Bit 5 | Accessed (A) | 访问位:硬件自动置 1,表示该页被读取过 |
| Bit 6 | Dirty (D) | 脏位:硬件自动置 1,表示该页被写入过 |
| Bit 7 | Page Attribute Table (PAT) | 页属性表索引,用于内存类型控制 |
| Bit 8 | Global (G) | 全局位:1 表示该页在 TLB 中不被进程切换刷新 |
| Bit 9-11 | Ignored / Available | 可供操作系统软件使用的位 |
| Bit 12-51 | Physical Address (PFN) | 物理页框号(物理地址右移 12 位),共 40 位 |
| Bit 52-62 | Reserved / Ignored | 保留位或供软件使用 |
| Bit 63 | No Execute (NX) | 禁止执行位:1 表示该页不可执行代码 |
Linux 内核视角:内核在
<asm/pgtable_types.h> 中定义了 PTE 的位掩码宏,如 _PAGE_PRESENT、_PAGE_RW、_PAGE_USER、_PAGE_ACCESSED、_PAGE_DIRTY、_PAGE_PROTNONE 等。页表项的修改必须使用原子操作,避免与硬件 TLB 填充产生竞争。
4.5 四者关系总结
页框、页、页表、页表项构成了分页机制的完整逻辑链条:
- 虚拟地址空间被划分为 页(虚拟页),每个页有唯一的 VPN。
- 物理内存被划分为 页框(物理页),每个页框有唯一的 PFN。
- 页表是存储在物理内存中的多级数据结构,以 VPN 为索引查找对应的 PTE。
- 页表项记录 PFN 和权限位,MMU 根据 PTE 中的 PFN 拼接页内偏移得到物理地址。
记忆口诀:“页框是房子,页是门牌号,页表是门牌簿,页表项是门牌簿上的一行记录。MMU 拿着门牌号查门牌簿,找到房子所在位置,然后走进去(访问物理内存)。”