Linux内核——X86分页机制

发布于:2025-04-10 ⋅ 阅读:(42) ⋅ 点赞:(0)

X86分页机制

x86的分页单元支持两种分页模式:常规分页与扩展分页。

常规分页采用两级结构,固定页大小为4KB。线性地址被划分为三个字段:

  • 页目录索引(最高10位)
  • 页表索引(中间10位)
  • 页内偏移(最低12位)

扩展分页启用时采用单级结构,页大小为4MB。线性地址被划分为两个字段:

  • 页目录索引(最高10位)
  • 页内偏移(最低22位)

页表结构

常规分页与扩展分页可混合使用。页目录条目中有一个标志位用于指定当前使用哪种分页模式。专用寄存器CR3指向页目录的基地址,页目录条目则指向页表的基地址。

页目录和页表均包含1024个条目,每个条目占4字节。
所有页表均存储于物理内存中,页表地址均为物理地址。

页表条目字段

  • 存在位(Present/Absent)
  • 页框号(PFN):物理地址的最高20位
  • 访问位(Accessed,硬件不自动更新,供操作系统维护使用)
  • 脏位(Dirty,硬件不自动更新,供操作系统维护使用)
  • 访问权限(读/写)
  • 特权级(用户/管理员)
  • 页大小标志(仅页目录条目有效,置位时启用扩展分页)
  • 缓存控制位(PCD-禁用页缓存,PWT-直写模式)

Linux分页实现

Linux采用四级分页模型以支持64位架构。下图展示了如何通过虚拟地址的各字段索引页表并计算物理地址:

Linux提供统一的API用于创建和遍历页表。内核与进程地址空间的创建及修改均通过通用代码实现,这些代码依赖宏和函数将通用操作适配到不同架构。

虚拟地址转物理地址示例(使用Linux页表API)

struct page *page; 
pgd_t pgd; 
pmd_t pmd; 
pud_t pud; 
pte_t pte; 
void *laddr, *paddr; 


pgd = pgd_offset(mm, vaddr);      // 获取页全局目录项 
pud = pud_offset(pgd, vaddr);     // 获取页上级目录项 
pmd = pmd_offset(pud, vaddr);     // 获取页中间目录项 
pte = pte_offset(pmd, vaddr);     // 获取页表项 
page = pte_page(pte);             // 获取对应物理页结构 
laddr = page_address(page);       // 获取逻辑地址 
paddr = virt_to_phys(laddr);      // 转换为物理地址 为兼容分页层级少于4级的架构(如x86 32位),部分宏/函数会被定义为空操作:



static inline pud_t *pud_offset(pgd_t *pgd, unsigned long address) { 
    return (pud_t *)pgd;  // 直接返回页全局目录项地址 

} 


static inline pmd_t *pmd_offset(pud_t *pud, unsigned long address) { 
    return (pmd_t *)pud;  // 直接返回页上级目录项地址 

} 

转译后备缓冲器(快表,TLB)

使用虚拟内存时,由于页表的多级结构,地址转换可能需要额外1次(x86扩展分页)、2次(x86常规分页)或3次(x86 64位)内存访问。

快表(TLB)作为专用缓存用于加速虚拟地址到物理地址的转换,其特性如下:

  • 缓存分页信息(页框号、权限、特权级)
  • 基于内容可寻址存储器(CAM)实现
  • 容量极小(64-128条目)
  • 速度极快(并行搜索实现单周期访问)
  • CPU通常包含独立指令TLB(i-TLB)与数据TLB(d-TLB)
  • TLB未命中惩罚:可达数百时钟周期

与其他缓存类似,需注意TLB的一致性问题。例如:

  • 修改页表条目使其指向新的物理地址时,必须使旧TLB条目失效,否则MMU仍会使用旧的物理地址转换。

x86平台支持两种TLB失效操作:

  1. 单地址失效
mov $addr, %eax 
invlpg (%eax)  ; 强制刷新指定虚拟地址的TLB条目 
  1. 全局失效
mov %cr3, %eax 
mov %eax, %cr3  ; 通过重载CR3寄存器刷新全部TLB