虚拟内存 - 常见错误

错误 1:启用分页后立即Triple Fault

症状

设置CR0的PG位后,系统立即重启。

原因

当前正在执行的代码没有被映射。启用分页后CPU立即使用虚拟地址,如果这些地址未映射到正确的物理内存,就会缺页→双重错误→Triple Fault。

解决方案

在启用分页之前,必须建立恒等映射(identity mapping),至少覆盖:

  • 内核代码所在的内存
  • 栈
  • VGA显存(如果需要输出)
  • 页目录和页表本身

错误 2:页目录/页表未对齐

症状

加载CR3后系统崩溃。

原因

页目录和页表必须4KB对齐(地址低12位为0)。

解决方案

// 使用页对齐的kmalloc
page_directory_t *dir = kmalloc_aligned(sizeof(page_directory_t));
page_table_t *table = kmalloc_aligned(sizeof(page_table_t));

错误 3:忘记设置Present位

症状

访问映射好的地址仍然产生缺页异常。

原因

页表条目的Present位未设置。

解决方案

// 确保flags中包含PAGE_PRESENT
table->entries[pt_idx] = (phys & 0xFFFFF000) | PAGE_PRESENT | PAGE_WRITABLE;
//                                               ^^^^^^^^^^^^^ 关键!

错误 4:TLB缓存导致映射不生效

症状

修改了页表但访问仍使用旧映射。

原因

CPU缓存了虚拟→物理的映射在TLB中。

解决方案

修改页表后刷新相应的TLB条目:

void vmm_flush_tlb(uint32_t virt) {
    __asm__ volatile("invlpg (%0)" : : "r"(virt) : "memory");
}

或重新加载CR3(刷新整个TLB):

__asm__ volatile("mov %0, %%cr3" : : "r"(dir->physical_addr));

错误 5:页目录/页表条目混淆

症状

映射看似正确但访问时出错。

原因

页目录条目应该指向页表的物理地址,而不是虚拟地址。

解决方案

在恒等映射环境下,虚拟=物理,所以问题不明显。但在高半内核中:

// 页目录条目必须使用物理地址
dir->entries[pd_idx] = phys_addr_of_table | flags;
// 内核访问页表使用虚拟地址
dir->tables[pd_idx] = virt_addr_of_table;

调试技巧

# QEMU显示页表内容
(qemu) info tlb       # 查看TLB
(qemu) info mem       # 查看内存映射
(qemu) xp /4x <cr3>   # 查看页目录内容
 
# GDB查看CR3
(gdb) p/x $cr3
(gdb) p/x $cr2        # 缺页异常的地址