何时做或不做INVLPG,MOV到CR3以最小化TLB刷新

时间:2015-02-07 16:00:13

标签: x86 paging x86-64 virtual-memory tlb

序言

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

从80386开始,英特尔的x86处理器系列及其各种克隆支持虚拟内存分页。众所周知,当PG中的CR0位置位时,处理器使用虚拟地址转换。然后,CR3寄存器指向顶级页面目录,该目录是将虚拟地址映射到物理地址的2-4级页表结构的根目录。

处理器不会为生成的每个虚拟地址查询这些表,而是将它们缓存在名为Translation Lookaside Buffer或TLB的结构中。但是,当对页表进行更改时,需要刷新TLB。在80386处理器上,这个刷新将完成 使用顶级页面目录地址或任务切换重新加载(MOVCR3。这应该无条件地刷新所有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,那么需要回答2个问题:

  1. 如果ISA上支持INVLPG,那么可以安全地使用它而不是重新加载CR3进行哪些更改?例如。 “如果一个取消映射一个页面框架(将相应的表条目设置为不存在),可以始终使用INVLPG”?

  2. 在不触及CR3或执行INVLPG的情况下,可以对表和目录进行哪些更改?例如。 “如果一个页面根本没有映射(不存在),那么就可以为它编写一个带有Present=1的PTE而无需刷新TLB”?

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

2 个答案:

答案 0 :(得分:24)

用最简单的术语;要求是,在发生任何依赖于变更的事情发生之前,CPU的TLB可以记住的任何变化都必须失效。

CPU可以记住的事情包括:

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

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

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

我已经选择了“在依赖变化的任何事情发生之前”这些词语。现代CPU(特别是对于长模式)确实缓存较高级别的分页结构(例如PDPT条目)而不仅仅是最终页面。这意味着如果您更改更高级别的分页结构但页面表条目本身保持不变,则仍需要使其无效。

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

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

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

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

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

答案 1 :(得分:4)

对你的第一个问题:

  1. 您可以随时使用INVLPG,您可以进行任何更改。永远保存使用INVLPG。
  2. 重新加载CR3不会使TLB中的全局页面无效。因此,有时您必须使用INVLPG作为重新加载CR3无效。
  3. INVLPG必须用于每个发布的页面。如果您一次更改多个页面,那么重新加载CR3的速度要快于多个INVLPG调用。
  4. 别忘了现代cpus上的Address Space IDentifier扩展。
  5. 关于你的第二个问题:

    未映射的页面无法缓存在TLB中(假设您在取消映射时将其置为无效)。因此,从不存在的任何更改都不需要INVLPG或CR3重新加载。