重新调度=从RS(调度程序)再次派遣。(这不是关于您整个问题的完整答案,只是关于重放的部分。虽然我认为这涵盖了大部分内容,包括解除依赖uops。)
此答案的部分对负载重播有误解。
请参见聊天中的讨论-依赖于分裂或缓存未命中负载的uops将被重播,但不会重播负载本身。(除非负载在循环中依赖于自身,例如我一直在测试的那样)。 TODO:修复此答案和其他答案的其余部分。
事实证明,缓存未命中加载不仅仅在加载缓冲区中等待数据到达时唤醒相关的 uop。调度程序必须重新分派加载 uop 来实际读取数据并写回物理寄存器。(并将其放在转发网络上,在下一个周期中可以读取依赖的 uop。)因此,L1 未命中 / L2 命中将导致 2 倍的加载 uop 被分派。(调度程序是乐观的,并且 L2 在核心上,因此 L2 命中的预期延迟是固定的,而不像离核心响应的时间。我不知道调度程序是否继续对从 L3 到达某个时间的数据保持乐观。)
RIDL论文提供了一些有趣的证据,表明负载uop确实直接与LFB交互,不等待传入数据被放置在L1d中,而是直接从那里读取。
我们可以最容易地在实践中观察到缓存行分裂加载的重放,因为反复引起这种情况甚至比缓存未命中更微不足道,需要更少的代码。仅执行拆分加载的循环,
uops_dispatched_port.port_2
和
port_3
的计数将约为两倍。(我已经在Skylake上实践验证了这一点,使用基本相同的循环和测试过程,如
How can I accurately benchmark unaligned access speed on x86_64所述)
检测到分裂的加载(仅在地址计算后可能)不会向RS返回成功完成的信号,而是执行第一部分数据的加载,并将其结果放入一个分裂缓冲区
1,以便在第二次uop调度时与第二个缓存行的数据结合。 (假设两次都不是缓存未命中,否则也需要重放。)
当一个负载uop调度时,调度器会预测它会命中L1d,并调度依赖的uops,以便它们可以在负载将它们放到总线上的那个周期内从转发网络中读取结果。
如果这种情况没有发生(因为负载数据还没有准备好),那么依赖的uops也将不得不重新执行。我记得这也可以通过端口的perf计数器观察到。
现有的关于Intel处理器上uop重播证据的问答:
注1:
我们知道分裂缓冲区数量有限;对于因缺乏缓冲区而停顿的加载,有一个ld_blocks.no_sr
计数器。我推断它们在加载端口中,因为这是有意义的。重新调度相同的加载uop将把它发送到同一加载端口,因为uop在发布/重命名时被分配给端口。虽然也许有一个共享的分裂缓冲区池。
RIDL:
乐观调度是导致问题的机制之一。更明显的问题是让后面的uop执行看到来自LFB的“垃圾”内部值,就像Meltdown中一样。
http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/甚至显示,PPro中的Meltdown加载公开了各种微架构状态的位,就像最新处理器中仍存在的此漏洞一样。
Pentium Pro对“负载值是不关心”的理解非常字面。对于所有禁止的负载,负载单元都会完成并生成一个值,并且该值似乎是从处理器的各个部分获取的各种值。该值是变化的,可能是非确定性的。返回的值中没有一个是内存数据,因此Pentium Pro似乎不容易受到Meltdown攻击。
可识别的值包括负载的PTE(至少在最近几年中,它本身被认为是特权信息),第12个最近存储的值(存储队列有12个条目)以及很少情况下从某个地方的段描述符。
(后来的CPU,从Core 2开始,公开了L1d高速缓存中的值;这就是Meltdown漏洞本身。但PPro / PII / PIII不容易受到Meltdown的攻击。在这种情况下,它显然容易受到RIDL攻击。)因此,这是相同的英特尔设计理念,将微架构状态的一些位暴露给了推测执行。在硬件上把它压制为0应该是一个简单的修复;负载端口已经知道它没有成功,所以根据成功/失败屏蔽加载数据的掩码只会增加几个门延迟,并且可以在不限制时钟速度的情况下实现。(除非负载端口中的最后一个管道级别已经是CPU频率的关键路径。)因此,对于未来的CPU来说,这可能是一个简单而廉价的硬件修复,但对于现有的CPU来说,通过微码和软件进行缓解非常困难。)