存储缓冲区和行填充缓冲区如何相互交互?

How do the store buffer and Line Fill Buffer interact with each other?

我正在阅读 MDS 攻击论文 RIDL: Rogue In-Flight Data Load. They discuss how the Line Fill Buffer can cause leakage of data. There is the 问题,该问题讨论了漏洞利用的微架构细节。

读完这个问题后我不清楚的一件事是,如果我们已经有一个存储缓冲区,为什么我们需要一个行填充缓冲区。

John McCalpin 在英特尔论坛 How does WC-buffer relate to LFB? 中讨论了存储缓冲区和行填充缓冲区的连接方式,但这并没有让我更清楚。

For stores to WB space, the store data stays in the store buffer until after the retirement of the stores. Once retired, data can written to the L1 Data Cache (if the line is present and has write permission), otherwise an LFB is allocated for the store miss. The LFB will eventually receive the "current" copy of the cache line so that it can be installed in the L1 Data Cache and the store data can be written to the cache. Details of merging, buffering, ordering, and "short cuts" are unclear.... One interpretation that is reasonably consistent with the above would be that the LFBs serve as the cacheline-sized buffers in which store data is merged before being sent to the L1 Data Cache. At least I think that makes sense, but I am probably forgetting something....

我最近才开始阅读乱序执行,请原谅我的无知。这是我对商店如何通过商店缓冲区和行填充缓冲区的想法。

  1. 在前端安排存储指令。
  2. 在存储单元中执行。
  3. 存储请求放入存储缓冲区(地址和数据)
  4. 无效读取请求从存储缓冲区发送到缓存系统
  5. 如果未命中 L1d 缓存,则将请求放入行填充缓冲区
  6. 行填充缓冲区将无效读取请求转发到 L2
  7. 一些缓存接收到无效读取并发送其缓存行
  8. 存储缓冲区将其值应用于传入缓存行
  9. 呃?行填充缓冲区将条目标记为无效

问题

  1. 如果存储缓冲区已经存在以跟踪超出的存储请求,为什么我们需要行填充缓冲区?
  2. 我描述的事件顺序是否正确?

Why do we need the Line Fill Buffer if the store buffer already exists to track outsanding store requests?

存储缓冲区用于按顺序跟踪存储它们退休之前和它们退休之后但在它们提交到L1缓存之前2。存储缓冲区在概念上是一个完全本地的东西,它并不真正关心缓存未命中。商店缓冲区处理各种规模的各个商店的“单元”。像 Intel Skylake 这样的芯片有 store buffers of 50+ entries.

行填充缓冲区主要处理 加载和存储 未命中的 L1 缓存 。本质上,它是从 L1 缓存到内存子系统其余部分的路径,并处理缓存行大小的单元。如果加载或存储命中 L1 缓存1,我们不希望 LFB 介入。像 Skylake 这样的英特尔芯片的 LFB 条目要少得多,大概有 10 到 12 个。

Is the ordering of events correct in my description?

非常接近。以下是我如何更改您的列表:

  1. 存储指令被解码并拆分为存储数据和存储地址微指令,它们被重命名、调度并为它们分配存储缓冲区条目。
  2. store uops 以任意顺序执行或同时执行(两个子项可以以任意顺序执行,主要取决于哪个先满足其依赖项)。
    1. 存储数据uop将存储数据写入存储缓冲区。
    2. 存储地址 uop 进行 V-P 转换并将地址写入存储缓冲区。
  3. 在所有较旧的指令都退出的某个时刻,存储指令退出。这意味着该指令不再是推测性的,并且可以使结果可见。此时,store 保留在 store buffer 中,称为 senior store.
  4. 存储现在一直等到它位于存储缓冲区的头部(它是最旧的未提交存储),此时它将提交(变得全局可观察)到 L1,如果关联的缓存行是以 MESIF Modified 或 Exclusive 状态存在于 L1 中。 (即这个核心拥有这条线)
  5. 如果该行未处于所需状态(完全缺失,即缓存未命中,或存在但处于非独占状态),则允许修改该行和行数据(有时)必须从内存子系统中获取:这会为整行分配一个 LFB(如果尚未分配的话)。这就是所谓的 所有权请求 (RFO),这意味着内存层次结构应该 return the line 处于适合修改的独占状态,而不是适合的共享状态仅用于读取(这会使任何其他私有缓存中存在的行副本无效)。

将 Shared 转换为 Exclusive 的 RFO 仍然需要等待响应以确保所有其他缓存都已使其副本无效。对此类无效的响应不需要包含数据副本,因为此缓存已经有一个。它仍然可以称为 RFO;重要的部分是在修改线路之前获得所有权。 6. 在未命中的情况下,LFB 最终返回该行的全部内容,该行已提交给 L1,待处理存储现在可以提交3.

这是该过程的粗略近似值。某些细节可能在某些或所有芯片上有所不同,包括不太了解的细节。

作为一个例子,在上面的顺序中,直到商店到达商店队列的头部才提取商店未命中行。实际上,商店子系统可能会实现一种 RFO 预取 类型,其中会检查商店队列中是否有即将到来的商店,如果行不存在于 L1 中,则会提早开始请求(在 x86 上,对 L1 的实际可见提交仍然必须按顺序发生,或者至少“好像”按顺序发生。

因此,请求和 LFB 使用可能最早在步骤 3 完成时发生(如果 RFO 预取仅在商店退休后应用),或者甚至可能早在 2.2 完成时发生,如果初级商店受预取。

作为另一个例子,第 6 步描述了从内存层次结构返回并提交到 L1 的行,然后存储提交。有可能挂起的存储实际上与返回的数据合并,然后写入 L1。即使在未命中的情况下,存储也可能离开存储缓冲区并简单地在 LFB 中等待,释放一些存储缓冲区条目。


1 对于命中 L1 缓存的存储,有一个 建议 实际上涉及 LFB:每个存储在提交到缓存之前实际上进入了一个组合缓冲区(可能只是一个 LFB),这样一系列针对相同缓存行的存储在缓存中组合在一起并且只需要访问 L1 一次。这尚未得到证实,但无论如何,它并不是 LFB 主要用途的一部分(更明显的是,我们甚至无法真正判断它是否正在发生)。

2 在之前和退休之前保存存储的缓冲区可能是两个完全不同的结构,具有不同的大小和行为,但在这里我们将它们称为一个结构。

3 所描述的场景涉及错过在存储缓冲区头部等待直到关联行 returns 的存储。另一种情况是将存储数据写入用于请求的 LFB,然后可以释放存储缓冲区条目。这可能允许在未命中过程中处理一些后续存储,但要遵守严格的 x86 排序要求。这可能会增加商店 MLP。

当 uops 到达分配器时,在 PRF + 退休 RAT 方案(从 SnB 开始)中,分配器在必要时咨询前端 RAT (F-RAT) 以重命名 ROB 条目(即当写入时到架构寄存器(例如 rax)执行)它分配给 ROB 尾指针处的每个 uop。 RAT 在 PRF 中保留了一个空闲和正在使用的物理目标寄存器 (pdsts) 的列表。 RAT returns 要使用的物理寄存器号是空闲的,然后 ROB 将它们放在相应的条目中(在 RRF 方案中,分配器向 RAT 提供要使用的 pdsts;RAT 无法select 因为要使用的 pdsts 本质上位于 ROB 的尾指针处)。 RAT 还使用分配给它的寄存器更新每个体系结构寄存器指针,即它现在指向包含程序顺序中最新写入数据的寄存器。同时,ROB 在保留站(RS)中分配一个条目。在存储的情况下,它将在 RS 中放置一个存储地址微指令和一个存储数据微指令。分配器还分配 SDB(存储数据缓冲区)/SAB(存储地址缓冲区)条目,并且仅在 ROB/RS/RAT/SDB/SAB 中的所有必需条目都可用时才分配

一旦在 RS 中分配了这些微指令,RS 就会读取其源操作数的物理寄存器并将它们存储在数据字段中,同时检查这些源 PR(物理寄存器)的 EU 写回总线关联的 ROB 条目和写回数据,因为它们被写回 ROB。然后 RS 安排这些微指令,以便在它们拥有所有完整的源数据时分派到存储地址和存储数据端口。

然后调度uops——存储地址uop进入AGU,AGU生成地址,将其转换为线性地址,然后将结果写入SAB。我认为在 PRF+R-RAT 方案中商店根本不需要 PR(这意味着在这个阶段不需要对 ROB 进行回写)但是在 RRF 方案中 ROB 条目被迫使用他们的嵌入式 PR 和所有内容(ROB / RS / MOB 条目)均由其 PR 编号标识。 PRF+R-RAT 方案的好处之一是可以扩展 ROB,从而扩展飞行中的微指令的最大数量,而不必增加 PR 的数量(因为会有不需要任何指令的指令),如果条目没有标识 PR,则所有内容都由 ROB 条目编号处理。

商店数据直接通过商店转换器 (STC) 到达 SDB。一旦它们被分派,它们就可以被释放以供其他 uops 重用。这样可以防止更大的 ROB 受到 RS 大小的限制。

然后地址被发送到 dTLB,然后它将从 dTLB 输出的物理标签存储在 L1d 缓存的 PAB 中。

分配器已经为该ROB条目分配了SBID和SAB/SDB中的相应条目(STA+STD微融合到一个条目中),它缓冲了AGU/TLB中分派执行的结果来自RS。这些商店位于 SAB / SDB 中,对应的条目具有相同的条目号。 (SBID),将它们链接在一起,直到 MOB 被退休单元通知哪些商店准备退休,即它们不再是推测性的,并且在指向 ROB 的 ROB 条目检索指针的 CAM 匹配时通知 MOB index/ID 包含在 SAB / SDB 条目中(在每个周期可以退出 3 微指令的 uarch 中,有 3 个退出指针指向 ROB 中最旧的 3 个未退出指令,并且只有 ROB 就绪位模式 0 ,0,1 0,1,1 和 1,1,1 允许退休指针 CAM 匹配继续进行)。在这个阶段,他们可以在ROB中退役(称为'retire / complete locally')并成为高级存储并被标记为高级位(STA为Ae位,STD为De位),并被慢慢调度到L1d缓存, 只要SAB / SDB / PAB中的数据有效即可。

L1d 缓存使用 SAB 中的线性索引解码标签数组中的一组数据,该集合将包含使用线性索引的数据,并在下一个循环中使用与索引值相同的相应 PAB 条目SBID 将物理标签与集合中的标签进行比较。 PAB 的全部目的是允许存储的早期 TLB 查找隐藏 dTLB 未命中的开销,而他们什么都不做等待成为高级,并允许在存储实际上仍然是推测性的时候进行推测性页面遍历。如果存储立即高级,那么这种早期 TLB 查找可能不会发生,它只是被分派,这时 L1d 缓存将解码标签数组集并并行查找 dTLB,PAB 被绕过。请记住,在执行 TLB 转换之前它不能从 ROB 退出,因为可能存在 PMH 异常代码(页面错误,或在执行页面遍历时需要设置读取的访问/脏位)或异常当 TLB 需要通过它在 TLB 条目中设置的访问/脏位写入时的代码。存储的 TLB 查找完全有可能总是发生在这个阶段,并且不会与设置解码并行执行(与加载不同)。当 PAB 中的 PA 变得有效(有效位在 SAB 中设置)并且它在 ROB 中准备好退休时,商店变得高级。

然后它会检查线路的状态。如果它是共享线路,则物理地址将发送到 RFO 中的一致性域(写入时始终为 RFO),并且当它拥有该线路的所有权时,它将数据写入缓存。如果该行不存在,则为该缓存行分配一个 LFB,并将存储数据存储在其中,并将请求发送到 L2,然后 L2 将检查 L2 中该行的状态并启动读取或 RFO环形IDI接口。

当 RFO 完成并且 LFB 中的一位表示它有权写入该行时,存储变得全局可见,这意味着 LFB 将在下一次侦听无效或驱逐时一致地写回(或一个命中,当数据被写入该行时)。如果在未命中的情况下继续获取行之前将其写入 LFB,则不认为它是全局可见的(不像 senior loads 在命中时退出或当LFB 由 L1d 缓存分配),因为可能有其他内核发起的其他 RFO 可能在当前内核请求之前到达 LLC 切片控制器,如果当前内核上的 SFENCE,这将是一个问题核心已基于此版本的 'globally visible' 退役而退役——至少这为处理器间中断提供了同步保证。全局可见是指如果另一个内核发生负载,存储的数据将被另一个内核读取的那一刻,而不是在一小段时间之后的那一刻,在此之前旧值仍将被其他内核读取。存储由 L1d 缓存在分配 LFB 时完成(或者在发生命中时将它们写入行)并从 SAB / SDB 中退出。当所有先前的存储都从 SAB / SDB 退出时,这就是 store_address_fence(不是 store_address_mfence)及其关联的 store_data_fence 可以分派到 L1d 的时候。 LFENCE 也将 ROB 指令流序列化更为实用,而 SFENCE/MFENCE 则不这样做,因为它可能会导致 ROB 中的全局可见性出现非常长的延迟,并且没有必要,不像高级负载会立即退休,所以选择 LFENCE 栅栏同时序列化指令流是有道理的。 SFENCE/MFENCE 在分配的所有 LFB 变为全局可见之前不要退出。

行填充缓冲区可以处于 3 种模式之一:读、写或写组合。 write line fill buffer的目的我认为是将多个存储到同一行的数据合并到LFB中,然后当行到达时,用从L2获取的数据填充非有效位。可能在这个阶段,它被认为是完成的,因此写入被批量满足,并且更早一个周期,而不是等待它们被写入行中。只要现在保证响应另一个核心的RFO,就可以写回缓存。 LFB 可能会保持分配状态,直到它需要被释放,从而允许对同一行的后续读取和写入稍快一些。读取行缓冲区可以更快地为读取未命中服务几个周期,因为它在行填充缓冲区中立即可用,但需要更长的时间将其写入缓存行然后从中读取。当内存为 USWC 类型时,会分配写入组合缓冲区,并允许写入立即得到满足并一次性刷新到 MMIO 设备,而不是具有多个核心->PCIe 事务并具有多个 PCIe 事务。 WC 缓冲区还允许从缓冲区进行推测性读取。通常在 UC 内存上不允许进行推测性读取,因为读取可能会改变 MMIO 设备的状态,而且 read/write 需要很长时间,以至于在它完成时,它将不再是推测性的,因此可能不值得额外的流量? LFB 可能是 VIPT(,V 是 intel 上的线性地址);我想它可以有物理和虚拟标签来消除进一步的 TLB 查找,但是当物理页面迁移到新的物理地址时必须协商一些东西。