Linux内核通过页表和页表缓存的配合,实现高效的虚拟地址到物理地址转换,这是内存管理子系统的核心功能之一,保障了多进程环境下内存的安全隔离与高效访问。

页表的基本概念与作用
页表是存储在内存中的数据结构,用于维护虚拟地址空间和物理地址空间的映射关系。Linux采用分页机制管理内存,将虚拟内存和物理内存都划分为固定大小的页,通常页大小为4KB。每个进程拥有独立的页表,进程切换时内核会切换对应的页表基址,从而实现进程间的虚拟地址空间隔离。
页表中的每一项称为页表项,页表项除了记录对应的物理页框号之外,还包含一系列权限控制位,比如是否可读、可写、是否存在于内存中。如果页表项标记为不存在,访问对应虚拟地址时会触发缺页异常,内核会处理异常并完成页面的加载或分配。
多级页表结构
为了节省页表占用的内存空间,Linux采用多级页表结构。以x86_64架构为例,通常使用四级页表,分别是PGD(页全局目录)、PUD(页上级目录)、PMD(页中间目录)、PTE(页表项)。虚拟地址会被拆分为多个偏移字段,依次索引每一级页表,最终找到对应的物理页框号。
这种分级结构的好处是,对于进程未使用的虚拟地址区间,不需要分配对应的下级页表,大幅减少了页表的内存开销。以下是四级页表地址拆分的简化示意:
| 字段 | 作用 | 位数 |
|---|---|---|
| PGD偏移 | 索引页全局目录项 | 9位 |
| PUD偏移 | 索引页上级目录项 | 9位 |
| PMD偏移 | 索引页中间目录项 | 9位 |
| PTE偏移 | 索引页表项 | 9位 |
| 页内偏移 | 定位物理页内的具体位置 | 12位 |
页表缓存(TLB)的原理
页表缓存的全称是转换检测缓冲区,也就是常说的TLB,它是CPU内部的一块高速缓存,专门用于缓存最近使用过的虚拟地址到物理地址的映射项。如果没有TLB,每次地址转换都需要多次访问内存中的各级页表,会带来很大的性能开销。
TLB的工作流程
当CPU需要转换一个虚拟地址时,首先会在TLB中查找是否存在对应的映射项:
- 如果找到匹配项,也就是TLB命中,直接得到物理地址,不需要访问内存中的页表,这个过程称为快速路径。
- 如果没有找到匹配项,也就是TLB未命中,就需要遍历内存中的多级页表完成地址转换,转换完成后会把新的映射项存入TLB,方便后续访问时使用。
TLB的缓存项同样包含权限信息,地址转换时也会同步检查权限,避免非法访问。当页表内容发生修改时,比如修改了页表项的权限或者映射关系,内核需要主动刷新TLB中对应的缓存项,否则会出现地址转换错误。
TLB刷新的场景
常见的需要刷新TLB的场景包括:
- 进程切换时,由于不同进程的页表不同,需要刷新整个TLB或者切换TLB的上下文标识。
- 修改了某个页表项的映射关系或者权限时,需要刷新该页表项对应的TLB缓存。
- 物理页面被回收或者重新分配时,对应的旧映射项需要从TLB中清除。
代码示例:查看页表相关内核结构
以下是Linux内核中部分页表和TLB相关的结构定义简化示例,帮助理解其实现逻辑:
// 简化的页表项结构定义(x86架构)
typedef struct {
unsigned long present : 1; // 页是否存在于内存中
unsigned long rw : 1; // 是否可写
unsigned long user : 1; // 是否为用户态可访问
unsigned long pwt : 1; // 页级写透位
unsigned long pcd : 1; // 页级缓存禁用位
unsigned long accessed : 1; // 是否被访问过
unsigned long dirty : 1; // 是否被修改过
unsigned long pat : 1; // 页属性表位
unsigned long global : 1; // 是否为全局页,切换进程时不刷新TLB
unsigned long ignored : 3; // 保留位
unsigned long page_frame : 40;// 物理页框号
unsigned long available : 11; // 内核可用位
unsigned long nx : 1; // 不可执行位
} pte_t;
// 简化的TLB刷新函数声明
void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr);
void flush_tlb_mm(struct mm_struct *mm);
两者的协同工作机制
页表和TLB是相辅相成的:页表是地址映射的权威数据源,所有映射关系最终都以页表的内容为准;TLB是页表的高频访问缓存,减少地址转换的延迟。内核在修改页表时,必须同步维护TLB的一致性,否则会导致地址转换错误,引发系统异常。
在多核系统中,TLB的维护更加复杂,一个CPU修改了页表后,还需要通过处理器间中断通知其他CPU刷新对应的TLB缓存,保证所有CPU看到的地址映射是一致的。这种协同机制保障了Linux内存在多进程、多核场景下的正确性和高效性。