gpt4 book ai didi

x86 - 何时执行或不执行 INVLPG,MOV 到 CR3 以最小化 TLB 刷新

转载 作者:行者123 更新时间:2023-12-03 01:16:16 28 4
gpt4 key购买 nike

序幕

我是一个操作系统爱好者,我的内核运行在 80486+ 上,并且已经支持虚拟内存。

从 80386 开始,Intel 的 x86 处理器家族及其各种克隆支持带分页的虚拟内存。众所周知,当PG位在 CR0设置后,处理器使用虚拟地址转换。然后,CR3 register 指向顶层页目录,也就是将虚拟地址映射到物理地址的 2-4 级页表结构的根目录。

处理器不会为生成的每个虚拟地址查询这些表,而是将它们缓存在名为 Translation Lookaside Buffer 的结构中。 , 或 TLB。但是,当对页表进行更改时,需要刷新 TLB。在 80386 处理器上,此刷新将由
重新加载 ( MOV ) CR3与顶级页目录地址,或任务开关。据说这会无条件地刷新所有 TLB 条目。据我了解,对于虚拟内存系统来说, 是完全有效的。总是重新加载 CR3 任何 改变。

这是一种浪费,因为 TLB 现在会抛出完全好的条目,因此在 80486 处理器中 INVLPG 介绍了指令。 INVLPG将使与源操作数地址匹配的 TLB 条目无效。

然而,从 Pentium Pro 开始,我们也有全局页面没有随着移动到 CR3 而刷新。或任务切换;和 AMD x86-64 ISA 说一些上层页表结构可能会被缓存并且不会被 INVLPG 失效。 .为了对每个 ISA 上需要什么和不需要什么有一个连贯的了解,人们真的需要为自 80 年代以来发布的大量 ISA 下载一份 1000 页的数据表,以阅读其中的几页,即使如此,这些文件似乎对于 TLB 失效以及如果 TLB 没有正确失效会发生什么情况要特别模糊。



为简单起见,可以假设 我们谈论的是单处理器系统 .此外,可以假设 更改页面结构后无需任务切换 . (因此 INVLPG 总是被认为至少与重新加载 CR3 寄存器一样好的选择)。

基本假设是需要重新加载 CR3在每次更改页表和页目录后,这样的系统将是正确的。但是,如果想要避免不必要地刷新 TLB,则需要回答以下两个问题:

  • 前提是INVLPG ISA 支持,经过什么样的更改可以安全地使用它而不是重新加载 CR3 ?例如。 “如果取消映射一个页框(将相应的表条目设置为不存在),则始终可以使用 INVLPG”?
  • 可以对表和目录进行什么样的更改而无需触及 CR3或执行 INVLPG ?例如。 “如果一个页面根本没有映射(不存在),你可以用 Present=1 为它写一个 PTE,而根本不刷新 TLB”?

  • 即使在阅读了大量 ISA 文档以及与 INVLPG 相关的所有内容之后在 Stack Overflow 上,我个人不确定我在那里展示的任何一个例子。确实,一个 notable post马上说:“我不知道你什么时候应该使用它,什么时候不应该使用它。”因此,您可以提供任何特定的、正确的示例,最好是记录在案的,并且适用于 IA32 或 x86-64。

    最佳答案

    用最简单的术语;要求是 CPU 的 TLB 可能记住的任何已更改的内容都必须在依赖于更改的任何内容发生之前失效。

    CPU 可能记住的内容包括:

  • 页面的最终权限(来自页表条目、页目录条目等的读/写/执行权限的组合); 包括 该页面是否存在(请参阅下面的警告)
  • 页面的物理地址
  • “已访问”和“脏”标志
  • 影响缓存的标志
  • 无论是普通页面还是大型(2 或 4 MiB)页面或巨大(1 GiB)页面

  • 警告:因为英特尔 CPU 不记得“不存在”页面,英特尔的文档可能会说将页面从“不存在”更改为“存在”时不需要无效。 Intel 的文档仅适用于 Intel CPU。它不适用于所有 80x86 CPU。某些 CPU(主要是 Cyrix)确实记得页面何时“不存在”,并且由于这些 CPU,在将页面从“不存在”更改为“存在”时必须使页面无效。

    请注意,由于投机性执行,您不能偷工减料。例如,如果您知道某个页面从未被访问过,则不能假设它不在 TLB 中,因为 TLB 可能是推测性获取的。

    我非常谨慎地选择了“在任何依赖于变化的事情发生之前”这个词。现代 CPU(尤其是长模式)确实缓存了更高级别的分页结构(例如 PDPT 条目),而不仅仅是最后的页面。这意味着如果您更改了更高级别的分页结构,但页表条目本身保持不变,您仍然需要失效。

    这也意味着如果没有任何依赖于更改,则可以跳过失效。一个简单的例子是访问和脏标志 - 如果您不依赖这些标志(以确定“最近最少使用”以及将哪些页面发送到交换空间),那么 CPU没有意识到你已经改变了它们。也可以(不推荐用于单 CPU,但非常推荐用于多 CPU)跳过 TLB 失效,如果 CPU 使用旧的/陈旧的 TLB 信息,则会出现页面错误,其中页面错误当且仅当确实有必要时,处理程序才无效。

    此外; “CPU 的 TLB 可以记住的任何东西”有点棘手。通常,操作系统会将分页结构本身映射到虚拟地址空间中,以允许快速/轻松地访问它们(例如,常见的“递归映射”技巧,您将页面目录伪装成页表)。在这种情况下,当您更改页面目录条目时,您需要使受影响的普通页面无效(如您所料),但您还需要使任何映射中影响的更改无效。

    使用哪个(INVLPG 或重新加载 CR3)有几个问题。对于单页 INVLPG 会更快。如果您更改页面目录(影响 1024 页或 512 页,取决于分页的风格),那么在循环中使用 INVLPG 可能会或可能不会比重新加载 CR3 更昂贵(这取决于 CPU/硬件和访问模式对于失效后的代码)。

    这还有两个问题。首先是任务切换。在使用不同虚拟地址空间的任务之间切换时,您必须更改 CR3。这意味着,如果您更改影响大区域(例如页面目录)的内容,您可以通过尽早执行任务切换来提高整体性能,而不是现在重新加载 CR3(用于失效)然后很快重新加载 CR3(对于任务切换) )。基本上,这是一个“一石二鸟”的优化。

    另一件事是“全局页面”。通常,所有虚拟地址空间(例如内核)中的页面都相同。当您重新加载 CR3 时(例如在任务切换期间),您不希望保持不变的页面的 TLB 无缘无故失效,因为这会对性能造成不必要的伤害。为了解决这个问题并提高性能,(对于 Pentium 和更高版本)有一个称为“全局页面”的功能,您可以在其中将这些公共(public)页面标记为全局页面,并且在您重新加载 CR3 时它们不会失效。在这种情况下,如果您需要使全局页面无效,则需要使用 INVPLG 或更改 CR4(例如禁用然后重新启用全局页面功能)。对于较大的区域(例如更改页面目录而不仅仅是一页),它与以前相同(在循环中使用 CR4 可能比 INVLPG 更快或更慢)。

    关于x86 - 何时执行或不执行 INVLPG,MOV 到 CR3 以最小化 TLB 刷新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28384234/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com