xv6 在 创建地址空间(Creating an Address Space) 的过程,主要涉及两个方面:

  1. 内核页表的建立(kernel address space)
  2. 用户进程页表的建立(user address space)

这两个过程都是通过一系列函数配合完成的,核心在于如何构建多级页表、设置映射、以及最终启用分页机制。下面我将分步详细说明:


🧠 一、内核地址空间的创建

🔹 1. main() 中调用 kvminit()

位于 kernel/main.c,初始化内核页表。

kvminit();       // 创建页表
kvminithart();   // 启用页表

🔹 2. kvminit()kvmmake()

pagetable_t kvmmake(void)

作用: 为内核创建页表,返回根页表地址。

主要步骤:

  • kalloc():分配一个物理页,作为 root page table;
  • 多次调用 kvmmap(),把下面内容映射到页表中:

    • 内核代码段(text, data)
    • 所有物理内存([0, PHYSTOP])
    • 外设内存地址空间(UART、PLIC 等 MMIO)

🔹 3. kvmmap()mappages()

int mappages(pagetable_t pagetable, uint64 va, uint64 pa, uint64 sz, int perm)

作用: 为一段虚拟地址 [va, va+sz) 映射到物理地址 [pa, pa+sz)

  • 对每个页大小(PGSIZE = 4096)调用一次 walk() 查找对应 PTE;
  • 若对应页表项不存在,则 walk() 中分配新的页表页(level 2/1);
  • 设置 PTE:

    • 将物理地址的 PFN 放入 PTE;
    • 设置权限标志位:PTE_R / PTE_W / PTE_X / PTE_V

🔹 4. walk() 函数

pte_t *walk(pagetable_t pagetable, uint64 va, int alloc)

作用: 模拟硬件多级页表遍历,返回某个虚拟地址对应的 PTE 地址。

  • 使用 Sv39 模式,将虚拟地址拆成三级索引,每级 9 位;
  • 如果中间页表页不存在(PTE_V == 0),并且 alloc == 1,就调用 kalloc() 分配;
  • 直接返回最后一层页表项地址。

🔹 5. kvminithart():启动分页

void kvminithart()
{
  w_satp(MAKE_SATP(kernel_pagetable));  // 写入 root 页表 PFN 到 satp
  sfence_vma();                         // 刷新 TLB
}

这步执行后,分页开启,虚拟地址 → 物理地址 翻译生效。


🧠 二、用户地址空间的创建

用户进程的页表是每个进程私有的,由 proc_pagetable() 创建。


🔹 1. 在进程创建时调用 proc_pagetable()

pagetable_t proc_pagetable(struct proc *p)

步骤:

  • 分配 root page table;
  • 拷贝 trampoline 和 trapframe 映射;
  • 不映射内核段!

🔹 2. 用户代码映射(比如 exec/load):

uvmalloc(pagetable, 0, sz, PTE_R|PTE_W|PTE_X)

为用户程序的代码段、数据段分配物理内存,并建立虚拟映射。

底层调用的是 mappages(),和内核页表一样,只是页表对象是用户的。


🔹 3. 用户页表激活

scheduler() 中运行进程前,使用 uvm_switch()

void uvm_switch(pagetable_t pagetable)
{
  w_satp(MAKE_SATP(pagetable));
  sfence_vma();
}

这使得 CPU 开始使用该用户页表。


🔄 页表更新与 TLB

任何对页表的改动(新增/删除 PTE),都要通过 sfence_vma() 来刷新 TLB,否则会使用旧缓存,可能导致严重安全漏洞(如访问别的进程的内存)。


✅ 总结:创建地址空间流程

阶段 关键函数 说明
创建内核页表 kvminit()kvmmake()kvmmap() 分配 root,映射内核地址空间
启用分页 kvminithart() 设置 satp,启用分页
创建用户页表 proc_pagetable() 用户进程私有页表
映射用户程序段 uvmalloc()mappages() 分配内存 + 映射
切换页表 uvm_switch() 写入 satp,刷新 TLB