内存分页

内存分页

内存分页(Paging) 是现代操作系统实现 虚拟内存(Virtual Memory) 的核心机制。它把程序使用的 虚拟地址空间(Virtual Address Space) 划分为固定大小的 页(Page),也把 物理内存(Physical Memory) 划分为同样大小的 页框(Frame / Page Frame)

随后,操作系统通过 页表(Page Table) 记录虚拟页到物理页框的映射关系。CPU 访问内存时,就可以根据这些映射完成 虚拟地址(Virtual Address)物理地址(Physical Address) 的转换

分页机制让每个进程都拥有看似连续、独立且受保护的地址空间。而真实的物理内存可以被操作系统灵活分配、回收、共享,必要时也可以换出到磁盘

页表

dream1page

系统运行时会产生大量虚拟页到物理页框的映射关系,这些关系必须被可靠记录和管理。分段机制可以用段寄存器描述少量内存段,但分页面对的是数量庞大的固定大小页面,远远超出寄存器能直接记录的范围

因此,分页通常使用页表来保存虚拟页与物理页框之间的映射关系。页表项除了记录目标页框,还会保存访问权限、是否存在、是否可写等控制信息

通常情况下,每个进程都有自己独立的页表。进程的虚拟地址空间被划分为若干固定大小的虚拟页,每个虚拟页都可以通过页表独立映射到物理内存中的某个页框

正因为这种独立映射关系,程序看到的虚拟内存可以是连续的,而实际对应的物理内存可以分散在不同位置,并不要求连续

:::code-group run=0

pub struct PageTable {
    entries: [PageTableEntry; 512],
}

pub struct PageTableEntry {
    entry: u64,
}

:::

多级页表

true1page

最直接的页表设计是单级页表:为虚拟地址空间中的每一个虚拟页都准备一个页表项,用来记录该虚拟页到物理页框的映射关系以及访问权限

单级页表结构简单,但它有一个明显缺点:页表大小取决于整个虚拟地址空间的规模,而不是进程实际使用了多少内存

例如,假设某进程拥有 1GB1\mathrm{GB} 的虚拟地址空间,页面大小为 4KB4\mathrm{KB},每个页表项占 8B8\mathrm{B}。整个虚拟地址空间会被划分为 1GB/4KB=2621441\mathrm{GB} / 4\mathrm{KB} = 262144 个虚拟页

因此,单级页表需要 262,144262{,}144 个页表项,页表大小约为 262144×8B2MB262144 \times 8\mathrm{B} \approx 2\mathrm{MB}

也就是说,即使进程实际只使用了其中很小一部分虚拟地址空间,单级页表仍然要为整个 1GB1\mathrm{GB} 地址范围预留页表项。随着虚拟地址空间变大,这种设计会带来明显的内存浪费

truemultipages

为了解决这个问题,操作系统引入了多级页表。多级页表不再使用一张连续的大表保存所有映射,而是把页表本身组织成分层结构

在多级页表中,高层页表项通常不直接指向物理页框,而是指向下一层页表。只有当某一段虚拟地址空间真正被使用时,操作系统才会为它分配对应的下级页表

仍以 1GB1\mathrm{GB} 虚拟地址空间、4KB4\mathrm{KB} 页面、8B8\mathrm{B} 页表项为例。在 x86_64 常见的四级页表结构中,每张页表大小为 4KB4\mathrm{KB},可容纳 4KB/8B=5124\mathrm{KB} / 8\mathrm{B} = 512 个页表项

如果进程实际只使用了一个 4KB4\mathrm{KB} 页面,那么只需要为这段地址建立一条从顶层页表到最低层页表的映射路径,即 P4+P3+P2+P1=4×4KB=16KB\mathrm{P4 + P3 + P2 + P1} = 4 \times 4\mathrm{KB} = 16\mathrm{KB}

相比单级页表约 2MB2\mathrm{MB} 的空间开销,多级页表在这种稀疏使用场景下只需要约 16KB16\mathrm{KB}。大量未使用的虚拟地址区域不再占用页表内存,从而显著降低空间开销

因此,从单级页表到多级页表的核心变化是:页表不再一次性覆盖整个虚拟地址空间,而是按需为实际使用的虚拟地址区域建立分层映射结构

页表访问

pageflowchart

仍以 x86_64 为例,当前顶级页表的地址由 CR3 寄存器提供,而 CR3 中保存的是物理地址。CPU 进行地址转换时,可以直接利用这个物理地址找到 P4 页表。随后,CPU 会根据各级页表项中保存的物理地址逐级查找下一层页表,直到找到目标虚拟页对应的物理页框

分页未开启时,CPU 访问内存时使用的地址不会再经过页表转换。此时程序给出的地址通常会直接对应到物理地址,或者只经过分段机制计算后得到最终的物理地址,因此内核可以把物理地址当作普通地址来访问

不过,分页开启后,内核代码进行普通内存访问时使用的是虚拟地址,不能直接把 CR3 中的物理地址当作指针访问

因此,操作系统需要建立额外的映射机制,把页表所在的物理内存映射到内核虚拟地址空间中。这样,页表项内部虽然仍保存物理地址,但内核可以通过对应的虚拟地址读取和修改页表

分页硬件优化

分页机制依赖硬件支持来降低地址转换成本。内存管理单元(MMU) 负责根据页表完成虚拟地址到物理地址的转换,并检查页表项中的访问权限

转换后备缓冲区(TLB) 用来缓存近期使用过的地址映射,避免每次访存都完整遍历多级页表。当页面不存在,或者访问权限不满足要求时,处理器会触发 缺页异常(Page Fault),交由操作系统处理

缺页异常不仅用于报告非法访问,也是实现按需分页、懒分配、页面换入换出以及写时复制等机制的基础

借助这些机制,操作系统可以在页面真正被访问时才分配或加载物理内存,也可以让多个进程共享页面,并在写入时再复制。由此可见,分页不仅是地址转换机制,也是虚拟内存抽象、内存隔离、权限保护和资源高效利用的重要基础