xv6 中地址空间的创建、TLB 处理与 sfence.vma 指令详解

1. 地址空间创建(Creating Address Space)

xv6 的地址空间通过多级页表(RISC-V Sv39,三级)实现虚拟地址到物理地址的映射。根据用途可分为:

  • 内核地址空间
  • 用户进程地址空间

a. 内核地址空间

系统启动时创建,仅一个实例,所有内核态共享。

kvmmake()

  • 创建内核页表的入口:

    kpgtbl = (pagetable_t) kalloc();
    memset(kpgtbl, 0, PGSIZE);
    
  • 使用 kvmmap() 建立各类内核映射。

kvmmap()

内部调用 mappages() 处理页表项,具体映射包括:

  • MMIO 映射(恒等映射)

    kvmmap(kpgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);
    kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
    kvmmap(kpgtbl, PLIC, PLIC, 0x4000000, PTE_R | PTE_W);
    
  • 内核代码段映射

    kvmmap(kpgtbl, KERNBASE, KERNBASE, etext - KERNBASE, PTE_R | PTE_X);
    
  • 内核数据段映射

    kvmmap(kpgtbl, etext, etext, PHYSTOP - etext, PTE_R | PTE_W);
    
  • trampoline 页(高地址处)

    kvmmap(kpgtbl, TRAMPOLINE, trampoline, PGSIZE, PTE_R | PTE_X);
    

b. 用户地址空间

每个进程独立拥有,生命周期随进程创建/销毁。

uvmcreate()

创建根页表页并清零,返回空页表。

地址空间填充相关函数:

  • uvmfirst():为第一个用户进程 init 分配页面并加载代码。
  • uvmalloc():进程扩展内存时分配新页并建立映射。
  • uvmcopy():fork 时复制父进程内存到子进程。

c. 页表操作机制

walk()

模拟三级页表查找过程:

  • 遇到缺失页表时可选分配。
  • 最终返回最底层 PTE 指针。

mappages()

结合 walk 为虚拟地址范围建立映射,设置权限。


2. TLB 的处理

a. 什么是 TLB?

  • TLB(Translation Lookaside Buffer)是 CPU 中的高速缓存。
  • 缓存最近使用的 VA→PA 映射(PTE)。

b. 为什么需要处理 TLB?

修改页表(如 mappages 或 uvmunmap)后:

  • TLB 中可能还存在过期映射
  • 若不刷新,CPU 可能使用旧权限或访问已释放页,造成错误。

解决方式:刷新 TLB,通知 CPU 不再使用旧条目。


3. sfence.vma 指令

RISC-V 的Supervisor Fence for Virtual Memory Access 指令,用于刷新 TLB。

a. sfence.vma 的作用

  • 保证之后的地址访问会使用最新页表
  • 防止使用已失效的页表缓存项

b. 在 xv6 中的使用

位于 kvminithart() 中,每个 CPU 启动时执行:

void kvminithart() {
  sfence_vma();  // 第一次:确保页表写入完成
  w_satp(MAKE_SATP(kernel_pagetable)); // 加载页表
  sfence_vma();  // 第二次:确保使用新页表,清除旧 TLB 项
}

执行顺序说明:

  1. 第一次 sfence_vma() 确保页表初始化已生效

  2. 设置 satp 启用分页并指定页表根地址

  3. 第二次 sfence_vma() 清除 TLB 中的旧条目,强制使用新页表


总结

  • 地址空间创建: 内核和用户通过页表映射管理内存
  • TLB 问题: 硬件缓存可能滞后,需及时清除
  • sfence.vma: 指令用于强制 TLB 刷新,确保页表更新生效