OS dev:尝试启用分页时出现三重故障

OS dev: triple fault when trying to enable paging

我正在构建一个简单的 OS 用于学习目的,并且我(目前;我之前学习了不同的教程并自己定制了一些东西)遵循 this 启用分页的教程。我使用 QEMU 而不是 Bochs 作为我的模拟器。

如果我继续禁用分页,一切正常(即使是我实现的非常基本的 kmalloc()),但是一旦我在 cr0 寄存器中设置 PG 位(即启用分页),一切都崩溃并且 QEMU 重新启动:我怀疑我的某些结构(即页目录、页表等)没有正确创建或加载,但我无法检查。

一段时间以来我一直在尝试解决这个问题,但还没有找到解决方案。谁能看出我的错误在哪里?

在这里您可以找到我的完整代码:https://github.com/davidedellagiustina/ScratchOS(提交 83b5c8c)。分页码位于src/cpu/paging.*.

编辑: 完全按照 this 教程设置超级基本页面目录会生成工作代码。基于这个简单的例子,我试图建立更复杂的结构(即 page_tpage_table_tpage_directory_t)以理解错误。

总的来说:

  • 指针应仅用于虚拟地址(绝不能用于物理地址)

  • 物理地址可能应该使用 typedef(例如 typedef uint32_t phys_address_t),以便稍后(当您想要支持 PAE/Physical 地址扩展时)您可以更改类型(例如,使用 typedef uint64_t phys_address_t 代替)而不破坏一切。这也意味着当你犯了愚蠢的错误时你会得到编译时 warnings/errors(例如,在你需要物理 address/unsigned 整数的地方使用虚拟 address/pointer)。

  • 几乎所有的内核都应该使用 pointers/virtual 地址。物理地址仅由某些设备驱动程序(用于总线 mastering/DMA)和物理内存管理本身使用(为页面 table 分配物理页面等;在将它们映射到虚拟地址之前 space).这包括高级内存管理("kmalloc()" 应该 return 一个 void * 指针而不是物理地址)。

  • 在启动过程中,有一小段时间 none 的内核正常代码可以工作,因为它使用虚拟地址并且分页尚未初始化。为了尽量减少这段时间的大小(以及由于具有 2 个版本的函数 - 一个用于 "before paging initialized" 和另一个用于 "after paging initialized" 引起的代码重复),您希望尽快初始化分页;使用在 "main()" 之前执行的专用汇编语言启动代码(可能使用内核的“.bss”部分中的 "statically allocated at compile time" 内存作为页面目录和页面 tables),或者在引导加载程序本身中(更干净,更多 powerful/flexible)。诸如设置有效内核堆栈和初始化(物理、虚拟然后是堆)内存管理之类的事情,can/should等到分页初始化之后。

  • 用于身份映射;您只需要 2 个循环(一个用于创建页面目录条目,另一个用于创建所有页面 table 条目),其中两个循环都可以像这样(只是在 eaxecxedi):

    .nextEntry:
        stosd
        add eax,0x00001000
        loop .nextEntry
    
  • 身份映射不是很好。通常你希望内核位于高虚拟地址(例如 0xC0000000),区域 "deliberately not used to catch NULL pointers" 在 0x0000000,而 user-space (进程等)在它们之间使用普通虚拟地址(例如可能从虚拟地址 0x00400000)。这使得初始化分页的代码和内核的链接器脚本很烦人(这就是为什么在引导加载程序中初始化分页并避免内核混乱更干净的原因)。对于这种情况;您将需要临时标识映射一页(该页面包含启用分页的最终“mov cr0”和将控制转移到更高地址的 code/the 内核的 jmp kernel_entry),并将想在内核的 main 启动后删除那个临时标识映射页。

  • 您需要 "very familiar" 具备模拟器的调试功能。 Qemu 有一个日志可以提供非常有用的线索,并且包括一个提供各种命令的内置监视器(参见 https://en.wikibooks.org/wiki/QEMU/Monitor )。您应该能够用无限循环 (.die: jmp die) 替换“mov cr0”(启用分页),然后在模拟器到达无限循环后使用监视器停止模拟器并检查所有内容(内容cr3,物理内存的内容)并找出页面目录或页面 table 条目有什么问题(并在启用分页后立即执行类似操作以检查之前的虚拟地址 space你的代码用它做任何事情)。 Qemu 还允许您附加远程调试器 (GDB)。

我发现我丢失了页面目录条目中的所有标志(尤其是 read/write 和内核模式的标志),因为我只放了页面 table 地址。我会保留我的仓库 public 并且从现在开始我会继续开发,以防将来有人需要它。

编辑:另外,我在创建新页面时忘记初始化所有页面(带有地址和存在位)table。