首页 文章

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

提问于
浏览
21

序幕

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

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

处理器不会为生成的每个虚拟地址查询这些表,而是将它们缓存在名为Translation Lookaside Buffer或TLB的结构中 . 但是,当对页表进行更改时,需要刷新TLB . 在80386处理器上,此刷新将通过使用顶级页面目录地址或任务切换重新加载( MOVCR3 来完成 . 这应该无条件地刷新所有TLB条目 . 据我了解,在更改之后,虚拟内存系统完全有效 any .

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

然而,从Pentium Pro开始,我们还拥有全局页面,这些页面不会被移动到 CR3 或任务切换;和AMD x86-64 ISA表示某些高级页面表结构可能会被缓存而不会被 INVLPG 无效 . 为了获得每个ISA所需内容和不需要内容的连贯图片,我们真的需要为80年代以来发布的大量ISA下载1000页的数据表来阅读其中的几页,即使这样,文档似乎也是如此对TLB失效特别模糊,如果TLB未正确无效,会发生什么 .

问题

为简单起见,可以假设 we are talking about a uniprocessor system . 此外,可以假设 no task-switch is required after changing the page structures . (因此 INVLPG 总是被认为至少是重新加载 CR3 寄存器的好选择) .

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

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

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

即使在Stack Overflow上阅读了相当多的ISA文档和与 INVLPG 相关的所有内容之后,我也不确定我在那里提供的任何一个例子 . 实际上,有一个notable post马上声明:"I don't know exactly when you should use it and when you shouldn't."因此,您可以提供的任何特定的,正确的例子,最好是文件记录,以及IA32或x86-64 .

2 回答

  • 4

    用最简单的术语;要求是CPU的TLB可以记住的任何已经改变的东西必须在依赖于改变的任何事情发生之前失效 .

    CPU可以记住的东西包括:

    • 页面的最终权限(页面表项的读/写/执行权限,页面目录条目等的组合); including 页面是否存在(参见下面的警告)

    • 页面的物理地址

    • "accessed"和"dirty"标志

    • 影响缓存的标志

    • 无论是普通页面还是大页面(2或4 MiB)或巨大(1 GiB)页面

    警告:因为在将页面从"not present"更改为"present"时,Intel CPU不需要使其无效 . 英特尔的文档仅适用于英特尔CPU . 对于所有80x86 CPU都不正确 . 某些CPU(主要是Cyrix)确实记得页面是"not present",并且由于这些CPU,您在将页面从"not present"更改为"present"时必须使其无效 .

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

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

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

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

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

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

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

  • 22

    对你的第一个问题:

    • 您可以随时使用INVLPG,您可以进行任何更改 . 使用INVLPG始终保存 .

    • 重新加载CR3不会使TLB中的全局页面无效 . 因此,有时您必须使用INVLPG作为重新加载CR3无效 .

    • INVLPG必须用于每个调用的页面 . 如果您一次更改多个页面,那么重新加载CR3的速度要快于多个INVLPG调用 .

    • 不要忘记现代cpu上的Address Space IDentifier扩展 .

    对于你的第二个问题:

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

相关问题