在 x86 上,启用分页是否会导致 "unconditional jump"(因为 EIP 现在是虚拟地址)?
On x86, does enabling paging cause an "unconditional jump" (since EIP is now a virtual address)?
当通过将 CR0 中的分页位设置为 1 来启用分页时,所有指针(包括 EIP)现在都被解释为虚拟地址而不是物理地址。除非 CPU 当前正在执行的内存区域是 "identity mapped"(虚拟地址映射到相同的物理地址),否则这似乎会导致 CPU 执行相当于"unconditional jump" -- 它应该从不同的(物理)地址开始执行代码。
这真的发生了吗?让 OS 启动代码可靠地处理此行为似乎非常棘手。还是所有保护模式 OS 的身份映射它们自己的内核代码?
这不需要完整的身份映射;例如,Linux 实际上一旦完成 运行 就完全丢弃了启动代码。 pmjump.S, where flat mode (32-bit identity map) is used and a jump is performed immediately after enabling protected mode. Notably that jump is written in machine code form due to the switch into 32-bit mode. From there, it proceeds via startup_32 to set up page tables. I'm not certain if the unconditional jumps after status changes are fully required (for instance, 32-bit real mode 中的相关代码是未按预期执行此类操作的意外副作用)。
是和否
是,在非正式意义上,因为现在 MMU 将虚拟地址转换为线性地址,并且 CPU 获取虚拟地址。如果我们在地址 4000h
处执行一条指令时打开分页,假设下一条指令位于 4003h
,则 4003h 可能会被翻译成 8003h
因此实际上会生成一个 从4000h
跳到8003h
。所以我们必须映射我们当前正在执行的页面,否则我们将不知道 CPU 将从哪里执行代码。
No,在技术意义上这不是跳转,因为 CPU 没有看到任何跳转指令及其所有副作用(比如丢弃 OoO 指令)此外 CPU 仅在整个缓存层次结构丢失后才访问内存,这意味着即使页面映射到不同的地址,您仍然可以执行来自 4003h
的指令。
那么,我们是否需要恒等映射?
是的,我们需要它。不是完整的身份映射,我通常只(身份)映射页面 7 和 8(对应线性范围 7000h-8fffh)例如。
比较启用分页和启用保护模式,您可以看出它们有多么不同。分页立即生效,因此您需要在激活它之前创建所有页表并且您需要至少一个标识页来处理您当前的运行 不依赖缓存的代码。
相反,启用保护模式更 "easy",您甚至可以在进入保护模式后 创建 GDT 条目 并且您可以通过更改段来控制何时首次使用它注册(通常 CS
跳转)。
实际上,如果您知道自己在做什么(比如通过复制代码或使用某些硬件内存别名),那么您并不严格需要身份页面,但在一般情况下,这是非常特定于上下文的没用的复杂。
经验答案:在这个最小分页示例中注释掉分页身份映射设置:https://github.com/cirosantilli/x86-bare-metal-examples/blob/24988411adf10cf9f6afd1566e35472eb8ae771a/paging.S#L79 并观察 OS 中断。所以是的,它 "jumps".
当通过将 CR0 中的分页位设置为 1 来启用分页时,所有指针(包括 EIP)现在都被解释为虚拟地址而不是物理地址。除非 CPU 当前正在执行的内存区域是 "identity mapped"(虚拟地址映射到相同的物理地址),否则这似乎会导致 CPU 执行相当于"unconditional jump" -- 它应该从不同的(物理)地址开始执行代码。
这真的发生了吗?让 OS 启动代码可靠地处理此行为似乎非常棘手。还是所有保护模式 OS 的身份映射它们自己的内核代码?
这不需要完整的身份映射;例如,Linux 实际上一旦完成 运行 就完全丢弃了启动代码。 pmjump.S, where flat mode (32-bit identity map) is used and a jump is performed immediately after enabling protected mode. Notably that jump is written in machine code form due to the switch into 32-bit mode. From there, it proceeds via startup_32 to set up page tables. I'm not certain if the unconditional jumps after status changes are fully required (for instance, 32-bit real mode 中的相关代码是未按预期执行此类操作的意外副作用)。
是和否
是,在非正式意义上,因为现在 MMU 将虚拟地址转换为线性地址,并且 CPU 获取虚拟地址。如果我们在地址 4000h
处执行一条指令时打开分页,假设下一条指令位于 4003h
,则 4003h 可能会被翻译成 8003h
因此实际上会生成一个 从4000h
跳到8003h
。所以我们必须映射我们当前正在执行的页面,否则我们将不知道 CPU 将从哪里执行代码。
No,在技术意义上这不是跳转,因为 CPU 没有看到任何跳转指令及其所有副作用(比如丢弃 OoO 指令)此外 CPU 仅在整个缓存层次结构丢失后才访问内存,这意味着即使页面映射到不同的地址,您仍然可以执行来自 4003h
的指令。
那么,我们是否需要恒等映射?
是的,我们需要它。不是完整的身份映射,我通常只(身份)映射页面 7 和 8(对应线性范围 7000h-8fffh)例如。
比较启用分页和启用保护模式,您可以看出它们有多么不同。分页立即生效,因此您需要在激活它之前创建所有页表并且您需要至少一个标识页来处理您当前的运行 不依赖缓存的代码。
相反,启用保护模式更 "easy",您甚至可以在进入保护模式后 创建 GDT 条目 并且您可以通过更改段来控制何时首次使用它注册(通常 CS
跳转)。
实际上,如果您知道自己在做什么(比如通过复制代码或使用某些硬件内存别名),那么您并不严格需要身份页面,但在一般情况下,这是非常特定于上下文的没用的复杂。
经验答案:在这个最小分页示例中注释掉分页身份映射设置:https://github.com/cirosantilli/x86-bare-metal-examples/blob/24988411adf10cf9f6afd1566e35472eb8ae771a/paging.S#L79 并观察 OS 中断。所以是的,它 "jumps".