【问题标题】:OS dev: triple fault when trying to enable paging操作系统开发人员:尝试启用分页时出现三重故障
【发布时间】:2020-05-31 14:50:44
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 您的真正意思是“教学目的”(意图使用操作系统来教别人),还是真正的意思是“仅供我自己学习”?
  • @Brendan 我的意思是仅供个人使用。已编辑帖子。
  • 假设您已经设置了一个有效的页面目录/表,在写入 CR3 寄存器(或通过 CR0 寄存器启用)之后,最常见的三重故障(重新启动)原因是写入之后的指令不再分页到它物理所在的位置。例如,您已将内核分页到 0xC0000000,但违规指令的物理地址仍位于分页启用之前的位置。您必须将该特定代码分页两次,一次在原始位置,一次在新位置,稍后删除旧位置。
  • 使用 QEMU 而不是 Bochs 为什么? BOCHS 的内置调试器了解分段和 x86 模式,因此与使用常规 GDB 的 QEMU 的 GDB-remote 相比,它更适合调试 early-init 的东西。我认为有某种方法可以让内置调试器捕获三重故障并让您进行调查。
  • @PeterCordes 我正在使用 QEMU,因为我之前遵循的教程使用了它。我刚开始时并不知道 Bochs。

标签: intel paging qemu virtual-memory osdev


【解决方案1】:

我发现我丢失了页面目录条目中的所有标志(尤其是读/写和内核模式的标志),因为我只放了页表地址。我将公开我的存储库,并且从现在开始我将继续开发,以防将来有人需要它。

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

【讨论】:

    【解决方案2】:

    一般:

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

    • 1234563打破一切。这也意味着当您犯愚蠢的错误(例如,在需要物理地址/无符号整数的地方使用虚拟地址/指针)时,您会收到编译时警告/错误。
    • 几乎所有的内核都应该使用指针/虚拟地址来处理一切。物理地址仅由某些设备驱动程序(用于总线主控/DMA)和物理内存管理本身(为页表等分配物理页面;在将它们映射到虚拟地址空间之前)使用。这包括高级内存管理(“kmalloc()”应该返回一个void * 指针而不是物理地址)。

    • 在引导过程中,有一小段时间内核的正常代码无法工作,因为它使用虚拟地址并且尚未初始化分页。为了最小化这段时间的大小(以及由于有两个版本的函数导致的代码重复——一个用于“分页初始化之前”,另一个用于“分页初始化之后”),您希望尽快初始化分页;使用在“main()”之前执行的专用汇编语言启动代码(可能使用内核的“.bss”部分中的“在编译时静态分配”内存用于页面目录和页表),或者在引导中loader 本身(更简洁、更强大/更灵活)。诸如设置有效内核堆栈和初始化(物理、虚拟然后堆)内存管理之类的事情可以/应该等到分页初始化之后。

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

      .nextEntry:
          stosd
          add eax,0x00001000
          loop .nextEntry
      
    • 身份映射不是很好。通常,您希望内核位于较高的虚拟地址(例如 0xC0000000),在 0x0000000 处具有“故意不用于捕获 NULL 指针”的区域,并且用户空间(进程等)在它们之间使用正常的虚拟地址(例如,可能从虚拟地址 0x00400000)。这使得初始化分页的代码和内核的链接器脚本很烦人(这就是为什么在引导加载程序中初始化分页并避免内核混乱的原因)。对于这种情况;您将需要临时标识映射一页(包含启用分页的最终“mov cr0”的页面和将控制权转移到更高地址的代码/内核的jmp kernel_entry),并希望删除该临时标识内核主程序启动后的映射页面。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-13
      • 1970-01-01
      • 2015-08-31
      • 2019-12-13
      相关资源
      最近更新 更多