L2 TLB未命中后会发生什么?

30

我不太理解当翻译辅助缓冲(TLB)的前两级未命中时会发生什么?

我不确定是否会在特殊硬件电路中进行“页游走”,或者页面表是否存储在L2/L3缓存中,或者它们只驻留在主内存中。

1个回答

41
(Some of this is x86 and Intel-specific. Most of the key points apply to any CPU that does hardware page walks. I also discuss ISAs like MIPS that handle TLB misses with software.)
现代x86微架构具有专用的页表遍历硬件。它们甚至可以在TLB缺失实际发生之前进行推测性的页表遍历以加载TLB条目。为了支持硬件虚拟化,页表遍历器可以处理主机VM中的客户机页表。(客户机物理内存=主机虚拟内存,或多或少。VMWare发布了一篇关于EPT概述和Nehalem基准测试的论文。)
Skylake甚至可以同时执行两个页表遍历,详见Intel的优化手册第2.1.3节。(Intel还将页面分裂加载惩罚从大约100降低到了大约5或10个额外的延迟周期,与缓存行分裂相当但吞吐量更差。这可能是相关的,或者添加第二个页表遍历单元是对在实际工作负载中比先前估计的页面分裂访问(和TLB未命中?)更重要的问题的另一个响应)。
一些微架构通过将其视为误预测来保护您免受推测性页表遍历攻击,当未缓存的PTE被推测性地加载但随后通过对页表的存储进行修改时,在第一个实际使用该条目之前,将其视为误预测。即,对于仅用于推测性TLB条目的页面表条目,会窥探对页面表条目的存储以进行处理,这些条目尚未被任何早期指令进行结构引用。
(Win9x依赖于此,不破坏重要的现有代码是CPU供应商关心的事情。当Win9x被编写时,当前的TLB失效规则尚不存在,因此这甚至不是一个错误;请参见下面引用的Andy Glew的评论)。AMD Bulldozer系列违反了这个假设,只给你在x86手册上所说的东西。
由页表硬件生成的页面表加载可以在 L1、L2 或 L3 缓存中命中。 例如,Broadwell perf counters 可以计算您选择的 L1、L2、L3 或内存(即缓存未命中)中的页面表加载命中次数。事件名称为 PAGE_WALKER_LOADS.DTLB_L1,代表 L1+FB 中 DTLB 页面行走器命中次数,其他事件则针对 ITLB 和其他级别的缓存。

现代页面表使用基数树格式,其中页面目录条目指向页面表条目的表,更高级别的PDE(页面目录条目)可以值得在页面遍历硬件中缓存。这意味着在某些情况下需要刷新TLB,即使你认为不需要。根据这篇论文(第3节),英特尔和AMD实际上已经这样做了。ARM也是如此,使用他们的中间表遍历缓存

那篇论文指出,在AMD CPU上,页面走动加载会忽略L1缓存,但会通过L2缓存。(可能是为了避免污染L1缓存,或减少读取端口的争用)。无论如何,这使得在页面走动硬件内缓存一些高级PDEs(每个涵盖许多不同的翻译项)变得更加有价值,因为使用指针链的代价随着延迟的增加而增加。

但需要注意的是,英特尔不保证负缓存TLB条目。将一个页面从无效变为有效不需要invlpg (因此,如果实际实现确实想要进行这种类型的负缓存,则必须进行嗅探或以某种方式仍然实现Intel手册所保证的语义。)

但是,有些旧的Cyrix CPU确实执行负缓存。然而,跨供应商的x86共同子集并不总是像Intel那样强大。64位内核应该可以安全地将PTE从不存在更改为存在,而无需invlpg,因为那些Cyrix芯片仅支持32位。(如果Intel、AMD和Via手册都同意它是安全的;我不知道其他x86-64供应商是否也同意。)


(历史注:Andy Glew在电子工程领域的一个类似问题的答案指出,在P5及之前的处理器中,硬件页表加载会绕过内部L1缓存(通常是写穿透的,这使得页表与存储一致)。我记得我的奔腾MMX主板上有L2缓存,可能是作为内存侧缓存。Andy还确认,P6及以后的处理器会从正常的L1d缓存中加载。

那个答案末尾有一些有趣的链接,包括我在上一段末尾链接的论文。它似乎也认为操作系统可以自己更新TLB,而不仅仅是页面表,在页面错误(HW pagewalk找不到条目)时,并且想知道x86是否可以禁用HW页表行走。(但实际上,操作系统只修改内存中的页面表,并从#PF返回,重新运行故障指令,因此HW pagewalk将在此次成功。)也许该论文考虑了像MIPS这样的ISA,其中软件TLB管理/缺失处理是可能的。

)
我认为在P5(或任何其他x86)上禁用HW页表步进实际上是不可能的。这将需要一种软件更新TLB条目的专用指令(没有这样的指令),或者使用wrmsr或MMIO存储。令人困惑的是,Andy在下面引用的帖子中说,软件TLB处理在P5上更快。我认为他指的是如果可能的话,它本来会更快。当时他在Imation(MIPS上)工作,在那里SW页面步进是一种选择(有时是唯一的选择),而不像x86。
或者他的意思是使用MSR提前设置TLB条目,以防预期不存在条目,从而避免一些页面访问。显然,386/486通过特殊寄存器具有TLB条目查询/设置访问功能: https://retrocomputing.stackexchange.com/questions/21963/how-did-the-test-registers-work-on-the-i386-and-the-i486 但是,在386/486上可能没有P5 MSR等效功能。 据我所知,在386/486甚至无法将TLB缺失陷阱传递到软件函数(禁用分页?),因此您无法完全避免硬件页面访问器,只能启动TLB以避免某些TLB缺失,至少在386/486上如此。
正如Paul Clayton指出(在另一个关于TLB缺失的问题上),硬件页表遍历的最大优势是TLB缺失不一定会使CPU停顿。乱序执行正常进行,直到重排序缓冲区因无法退役负载/存储而填满。退役按顺序进行,因为如果之前的指令出现故障,CPU不能正式提交任何不应该发生的操作。

顺便说一下,可能可以构建一个处理TLB缺失的x86 CPU,通过陷入微码而不是具有专用硬件状态机。这将(更)低效,并且可能不值得触发推测性执行(因为从微码中发出uop意味着您不能从正在运行的代码中发出指令)。

Microcoded TLB处理在理论上可以通过在一个单独的硬件线程中运行这些uops(有趣的想法)来避免可怕的情况,类似于SMT。您需要它比正常的超线程具有更少的启动/停止开销,以便从单线程切换到两个逻辑核心都处于活动状态(必须等待事物排空,直到可以分区ROB、存储队列等),因为它将与通常的逻辑核心相比,经常启动/停止。但是如果它不是真正的完全独立的线程,而只是一些单独的退役状态,那么它可能是可能的,因此其中的缓存未命中不会阻止主代码的退役,并且让它使用一些隐藏的内部寄存器用于临时。CPU设计人员选择要运行的代码,因此额外的HW线程不必接近x86核心的全部体系结构状态。它很少需要进行任何存储(也许只是用于PTE中的访问标志?),因此让这些存储使用与主线程相同的存储队列可能并不糟糕。您只需将前端分区以混合TLB管理uops并允许它们与主线程无序执行即可。如果您可以保持每个页面行走的uop数量较小,则可能不会很糟糕。

据我所知,目前没有任何CPU实际上使用微码在单独的硬件线程中进行“HW”页表步进,但这是一种理论可能性。


软件处理TLB:一些RISC架构是这样的,而不是x86

在一些RISC架构(例如MIPS)中,操作系统内核负责处理TLB缺失。TLB缺失会导致执行内核的TLB缺失中断处理程序。这意味着在这样的架构上,操作系统可以自由定义自己的页表格式。我猜,在CPU不知道页表格式的情况下,将页面标记为脏页后进行写入也需要陷入到操作系统提供的例程中。

这篇来自操作系统教材的章节讲解了虚拟内存、页表和TLB。它们描述了软件管理的TLB(MIPS,SPARCv9)和硬件管理的TLB(x86)之间的区别。一篇论文《研究几个内存管理单元、TLB填充机制和页面表组织》展示了Ultrix中TLB未命中处理程序的一些示例代码,如果您需要一个真正的例子。


其他链接


关于TLB一致性的评论,来自安迪·格卢(Andy Glew),英特尔P6架构师之一(Pentium Pro / II / III),后来在AMD工作。

主要原因是性能,因此英特尔开始通过高速缓存运行页表查找,而不是绕过高速缓存。在P6之前,页表查找速度较慢,无法从缓存中获益,并且是非推测的。足够慢以至于软件TLB未命中处理可以提高性能。 P6通过使用缓存并缓存中间节点(例如页面目录条目)来加快TLB未命中的速度。顺便说一句,AMD不愿意进行TLB未命中的推测处理。我认为这是因为他们受到DEC VAX Alpha架构师的影响。 DEC Alpha架构师之一曾经非常强烈地告诉我,例如P6所做的那样,对TLB未命中进行推测处理是不正确的,永远不会奏效。当我在2002年左右加入AMD时,他们仍然有一个称为“TLB Fence”的东西-不是fence指令,而是在rop或微代码序列中允许或不允许发生TLB未命中的点-恐怕我不记得它确切的工作方式了。因此,我认为Bulldozer放弃了TLB和页面表步行一致性并不是很重要,无论那意味着什么,因为Bulldozer可能是第一台进行中等程度的TLB未命中处理的AMD机器。请记住,当P6开始时,P5尚未发货:现有的x86都执行绕过缓存的页面表步行,以顺序方式,非推测性地,没有异步预取,但在写入缓存时。也就是说,它们是高速缓存一致的,并且操作系统可以依靠确定性地替换TLB条目。我IRC编写了关于TLB条目以及数据和指令高速缓存的推测和非确定性可缓存性的架构规则。您不能责怪像Windows、UNIX和Netware这样的操作系统不遵循当时不存在的页面表和TLB管理规则。

注1: 这是我之前提到的令人惊讶的说法,可能指使用MSRs来预先填充TLB,以希望避免一些页面访问。


更多来自Andy Glew 同一主题的内容, 因为这些评论值得被整理成一个完整的答案。

(2)就P6而言,我最大的遗憾之一是我们没有提供内部指令TLB一致性支持。有些指令会多次访问同一页。在同一指令中,不同的uop可能会为相同地址获得不同的翻译。如果我们赋予微码保存物理地址翻译的能力,然后使用它,我认为情况会更好。
(2a)当我加入P6时,我是RISC拥护者,我的态度是“让软件(微码)去做”。
(2a')最尴尬的错误之一与带进位加法到内存有关。在早期的微码中,加载将进行,进位标志将被更新,存储可能会出错 - 但进位标志已经被更新,因此指令无法重新启动。//这是一个简单的微码修复,将存储放在写入进位标志之前 - 但一个额外的uop足以使该指令不适合“中速”ucode系统。
(3)无论如何 - P6及其后代处理TLB一致性问题的主要“支持”是在报告故障之前在退休时重新遍历页表。这避免了通过报告页表中不应该有故障的信息来混淆操作系统。
(4)元注释:我认为任何体系结构都没有正确定义无效TLB条目的缓存规则。//据我所知,除了Itanium的NAT(Not A Thing)页面可能会缓存无效的TLB条目外,大多数处理器都不会缓存无效的TLB条目。但确实需要:推测性内存访问可能是到野地址,错过TLB,做昂贵的页表遍历,减慢其他指令和线程 - 然后一遍又一遍地这样做,因为“这是一个坏地址,不需要遍历页表”的事实没有被记住。//我怀疑DOS攻击可以利用这一点。
(4')更糟糕的是,操作系统可能会做出隐含的假设,即无效的翻译从不被缓存,因此在从无效到有效进行转换时不进行TLB失效或MP TLB清除。//更糟糕的是2:想象一下,您正在缓存页面表缓存的内部节点。想象一下,PD包含所有无效的PDE;更糟糕的是3,PD包含有效的d PDE,它们指向所有无效的PT。您还能缓存这些PDE吗?操作系统何时需要使条目无效?
(4'')由于使用处理器间中断进行MP TLB清除非常昂贵,因此操作系统性能人员(如我曾经)总是提出诸如“在将PTE从无效更改为有效后,我们不需要使TLB失效”或者“从只读有效更改为具有不同地址的有效可写”。或者“我们不需要在将PDE更改为指向与原始PT完全相同的不同PT之后使TLB失效...”。//有许多伟大的巧妙论点。不幸的是,不总是正确的。
(5)我的一些计算机架构师朋友现在主张一致的TLB:像数据缓存一样窥探写入的TLB。主要是为了让我们构建更加激进的TLB和页表缓存,如果叶子节点和内部节点的有效和无效条目都存在。并且
这个评论串是关于Andy对自修改代码和查看过时指令的答案的讨论,这是另一种情况,真正的CPU超越了纸上的要求,因为总是在EIP/RIP附近侦听存储比仅在分支指令上重新同步更容易,如果你没有跟踪分支之间发生的事情。

好的回答。通常调用操作系统进行页面遍历对性能非常不友好,因此大多数架构将其保留为特殊情况,例如页面故障。 - Leeor
1
@PeterCordes:你说硬件页表遍历器的一个优点是它可以与来自同一程序的其他代码同时运行,而与之相比,软件或微码则必须停止原始代码。在我熟悉的所有当前机器上都是如此,但它并不需要这样:考虑将TLB miss处理为另一个硬件线程。 - Krazy Glew
1
@LewisKelsey:有趣。我猜这是一种触发#PF或#UD的机制,一旦这个仍然具有推测性的代码获取到退休。但请记住,页面-故障直到页面行走完成后才能被检测到(因为TLB不进行负缓存),而推测性的早期页面行走是非常允许和鼓励的,所以这对我来说似乎并不与我为硬件页行走所建议的相冲突。我非常确定硬件页行走不涉及正常的uops,这些uops出现在uops_executed.any或其他正常计数器中,甚至不包括uops_dispatched_port.port_2或3。 - Peter Cordes
1
@LewisKelsey:页表使用物理地址;在遍历期间,您无法出现故障(除了需要触发辅助以设置A位和可能需要为存储器设置D位)。 遍历的结果可能是没有有效映射,因此触发遍历的加载、存储或代码获取应该出现故障(如果它们被证明是执行的真实路径)。 对于代码获取,前端在等待需求缺失页表遍历时无法执行其他操作;如果页表遍历结果返回无效(或需要辅助),则可以在此时等待并插入一个uop。 这是我的猜测。 - Peter Cordes
1
@PeterCordes:一些80x86 CPU会进行“负缓存”(特别是旧的Cyrix芯片)。英特尔承诺英特尔CPU不会进行“负缓存”,但英特尔(和英特尔的手册)不能代表其他供应商(AMD、VIA、Cyrix、IBM、SiS、NexGen等)。 - Brendan
显示剩余14条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接