【问题标题】:What happens to software interrupts in the pipeline?流水线中的软件中断会发生什么?
【发布时间】:2019-06-22 23:33:22
【问题描述】:

读完后:

When an interrupt occurs, what happens to instructions in the pipeline?

关于软件中断会发生什么的信息不多,但我们确实了解到以下内容:

相反,异常,比如页面错误,会标记受影响的指令。当该指令即将提交时,异常之后的所有后续指令都将被刷新,并重定向指令获取。

我想知道流水线中的软件中断(INT 0xX)会发生什么,首先,它们是什么时候检测到的?它们是在预解码阶段检测到的吗?在指令队列中?在解码阶段?还是他们到后端马上完成(不要进入预约站),依次retire,retirement阶段捡到是INT指令(好像很浪费)。

假设它在预解码时被拾取,必须有一种方法通知 IFU 停止获取指令或确实时钟/电源门控,或者如果它在指令队列中被拾取,则必须有一种方法在它之前刷新指令在队列中。然后必须有一种向某种逻辑(“控制单元”)发送信号的方式,例如为软件中断生成微指令(索引到 IDT,检查 DPL >=CPL >=segment RPL 等),天真建议,但如果有人对这个过程有更好的了解,那就太好了。

我还想知道当这个过程受到干扰时它如何处理它,即发生硬件中断(记住陷阱不会清除 EFLAGS 中的 IF),现在必须开始一个全新的中断处理和 uop 生成过程,之后它将如何恢复到处理软件中断的状态。

【问题讨论】:

  • 执行引用答案中的语句“大多数机器丢弃管道中的所有指令,在中断逻辑所在的管道阶段之前的管道阶段”“中断逻辑通常位于流水线的最后阶段 WB,大致对应于高级机器的提交流水线阶段。” 不回答这个问题?
  • @PatrickRoberts 第一个我几乎已经知道了,第二个,谢谢你发现它,我一定已经略过了。我仍然不确定最后一段中的困境,当然它没有解决这个问题。我想知道处理这个问题的控制单元的细节
  • @PatrickRoberts 但更重要的是,在它丢弃之后的那些指令是什么,这是否意味着 BPU 能够将管道重新导向 IDT 中的地址?否则它们将是什么指令,如果它们将被丢弃,那么获取它们有什么意义,在管道中更早地捕获软件中断不是更有效

标签: x86 intel pipeline interrupt-handling


【解决方案1】:

我同意彼得在回答中所说的一切。虽然它们可以通过多种方式实现INTn 指令,但很可能会针对 CPU 设计的简单性而非性能进行调整。可以非推测性地确定存在此类指令的最早点是在流水线的解码阶段结束时。可以预测获取的字节是否可能包含可能引发异常或确实引发异常的指令,但我找不到一篇研究这个想法的研究论文,所以这似乎不值得。

INTn 的执行涉及从 IDT 获取指定条目,执行许多检查,计算异常处理程序的地址,然后告诉获取单元从那里开始预取。这个过程取决于处理器的操作模式(实模式、64 位模式等)。该模式由来自CR0CR4Eflags 寄存器的多个标志描述。因此,实际调用异常处理程序需要很多微指令。在 Skylake 中,有 4 个简单解码器和 1 个复杂解码器。简单的解码器只能发出一个融合的微指令。复杂的解码器可以发射多达 4 个融合的微指令。它们都不能处理INTn,因此需要使用 MSROM 才能执行软件中断。请注意,INTn 指令本身可能会导致异常。此时,尚不清楚INTn 本身是否会将控制权更改为指定的异常处理程序(无论其地址是什么)或其他一些异常处理程序。可以肯定的是,指令流肯定会在INTn 结束并从其他地方开始。

激活微码定序器有两种可能的方式。第一个是在解码需要超过 4 uop 的宏指令时,类似于rdtsc。第二个是当撤销一条指令并且至少它的微指令在它的 ROB 条目中有一个有效的事件代码时。根据this专利,软件中断有一个专用的事件代码。所以我认为INTn 被解码为一个带有中断向量的微指令(或最多 4 微指令)。 ROB 已经需要一个字段来保存描述相应指令是否引发异常以及什么样的异常的信息。相同的字段可用于保存中断向量。 uop 只是通过分配阶段,可能不需要被调度到执行单元之一,因为不需要进行计算。当 uop 即将退役时,ROB 确定它是INTn,并且它应该引发一个事件(参见专利中的图 10)。此时,有两种可能的方法:

  • ROB 调用通用微码辅助,该辅助首先检查处理器的当前操作模式,然后选择与当前模式对应的专用辅助。
  • ROB 单元本身包括检查当前操作模式并选择相应辅助的逻辑。它将辅助地址传递给负责引发事件的逻辑,该逻辑又指示 MSROM 发出存储在该地址的辅助例程。此例程包含获取 IDT 条目并执行其余异常处理程序调用过程的微指令。

在辅助执行过程中,可能会出现异常。这将像任何其他导致异常的指令一样处理。 ROB 单元从 ROB 中提取异常描述并调用辅助来处理它。

可以以类似的方式处理无效的操作码。在预编码阶段,唯一重要的是正确确定无效操作码之前的指令长度。在这些有效指令之后,边界就无关紧要了。当一个简单的解码器接收到一个无效的操作码时,它会发出一个特殊的 uop,其唯一目的只是引发一个无效的操作码异常。负责最后一条有效指令之后的指令的其他解码器都可以发出特殊的微指令。由于指令按顺序退出,因此可以保证第一个特殊 uop 将引发异常。当然,除非之前的 uop 引发了异常,或者发生了分支错误预测或内存排序清除事件。

当任何解码器发出该特殊微指令时,获取和解码阶段可能会停止,直到确定宏指令异常处理程序的地址。这可能是由 uop 指定的异常或其他一些异常。对于处理该特殊 uop 的每个阶段,该阶段只能自行停止(断电/时钟门)。这样可以节省电力,我认为它很容易实现。

或者,如果另一个逻辑核心处于活动状态,则将其视为该逻辑线程放弃其前端周期给另一个超线程的任何其他原因。分配周期通常在超线程之间交替,但是当一个线程停止时(例如 ROB 已满或前端为空),另一个线程可以在连续的周期中进行分配。这也可能发生在解码器中,但也许可以使用足够大的代码块进行测试,以阻止它从 uop 缓存中运行。 (或者太密集而无法进入 uop 缓存)。

【讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • @HadiBrais 如果您考虑一下,解码器必须发出 uops 以进行推送和弹出。如果它看到 push eip 并且它处于实模式例如它必须发出mov [sp-2], ipsp-2,然后sp-2 被堆栈引擎删除,整个事情变成mov[addr], ip。也许解码器不需要帮助来检查模式,只需自己跟踪模式(通过检测 IA32_EFER.LME 或 PE 位指令)并在检测到它们时设置一个私有位并相应地发出指令并让 OoO 核心赶上来,直到退役,模式才正式改变。
  • 所以它停止,根据它检测到的模式发出 1 个 uop(每种模式 1 个),这会导致异常,解码器发出与 uop 对应的例程。如果它跟踪它之前的条件分支指令的数量,它可能会选择停止/不停止
  • @LewisKelsey 对。对于像PUSH 这样的简单指令,在所有操作模式下,微指令的数量都是相同的。微指令也可能相同,因为这可能会减少要编码的微指令总数,从而减少对所有微指令进行编码所需的位数。只有在需要操作数的大小时才需要检查模式,即在读/写寄存器/内存时。但更复杂的指令,如FXSAVE,可能需要不同模式下不同数量的微指令,这可以通过讨论的不同方式实现
  • ...在我的回答中。
【解决方案2】:

Andy @Krazy Glew 的这句话是关于在执行“正常”指令期间发现的同步异常,例如 mov eax, [rdi] 如果发现 RDI 指向未映射的页面,则会引发 #PF。1 sup> 你希望这不会出错,所以你推迟做任何事情直到退休,以防它处于分支错误预测或早期异常的阴影中。


但是是的,他的回答没有详细说明管道如何针对同步int 陷阱指令进行优化,我们知道在解码时总是会导致异常。陷阱指令在整个指令组合中也非常少见,因此针对它们进行优化并不能节省很多功率;只有做容易的事才值得。

正如 Andy 所说,当前的 CPU 不会重命名特权级别,因此无法推测中断/异常处理程序,因此在看到 intsyscall 之后停止获取/解码绝对是明智的。我只是要写int 或“陷阱指令”,但syscall/sysenter/sysret/iret 和其他改变特权的“分支”指令也是如此。而1-byte versions of int 就像int3 (0xcc) 和int1 (0xf1)。有条件的溢出陷阱into 很有趣;对于无陷阱情况下的非可怕表现,可能假定不陷阱。 (当然还有 vmcall 和 VMX 扩展的东西,可能还有 SGX EENTER,可能还有其他东西。但就停止管道而言,我猜所有陷阱指令都是平等的,除了条件into)


我假设像 lfence 一样,CPU 不会推测通过陷阱指令。你是对的,将这些微指令放在管道中是没有意义的,因为int 之后的任何内容肯定会被刷新。

IDK 如果在int 指令在后面变为非推测性之前从 IVT(实模式中断向量表)或 IDT(中断描述符表)获取 int 处理程序的地址 -结尾。可能。 (一些陷阱指令,如syscall,使用 MSR 来设置处理程序地址,因此从那里开始获取代码可能很有用,特别是如果它提前触发 L1i 未命中。这必须权衡看到 @ 的可能性987654349@ 和其他错误路径上的陷阱指令,在分支未命中后。)

错误推测命中陷阱指令的情况可能很少见,因此值得在前端看到陷阱指令后立即开始从 IDT 加载或预取 syscall 入口点,如果前端- end 足够聪明来处理这一切。但它可能不是。将花哨的东西留给微码对于限制前端的复杂性是有意义的。陷阱很少见,即使在 syscall-heavy 工作负载中也是如此。批处理工作以跨用户/内核障碍以更大的块移交是一件好事,因为便宜的syscall 在 Spectre 之后非常非常困难......


所以最迟会在 issue/rename 中检测到一个陷阱(它已经知道如何为(部分)序列化指令停止),并且不会将更多的微指令分配到输出中。无序后端,直到 int 退出并且正在处理异常。

但是在解码中检测到它似乎是可能的,并且不会进一步解码超过肯定发生异常的指令。 (而且我们不知道接下来从哪里获取。)解码器阶段确实知道如何停止,例如用于非法指令陷阱。

假设它是在预解码时提取的

这可能不实用,在完全解码之前你不知道它是int。预解码只是在 Intel CPU 上查找指令长度。我假设intsyscall 的操作码只是长度相同的众多操作码中的两个。

在硬件中构建以更深入地搜索陷阱指令将花费比预解码更多的功率。 (请记住,陷阱非常少见,早期检测它们大多只能节省电量,因此在将陷阱传递给解码器后停止预解码所节省的电量不会超过寻找它们的电量。

您需要对int 进行解码,以便其微码可以执行并让 CPU 再次开始运行中断处理程序,但理论上是的,您可能会在 传递之后的循环中出现预解码停顿它通过了。

例如,在常规解码器中,分支预测错过的跳转指令被识别出来,因此主解码阶段通过不进一步处理陷阱更有意义。 p>


超线程

当您发现一个档位时,您不只是对前端进行电源门控。您让其他逻辑线程拥有所有循环。

超线程降低了前端在没有后端帮助的情况下从 IDT 指向的内存中获取的价值。如果其他线程没有停止,并且可以在该线程整理其陷阱时从额外的前端带宽中受益,则 CPU 正在做有用的工作。

我当然不会排除从 SYSCALL 入口点获取代码的可能性,因为该地址位于 MSR 中,并且它是在某些工作负载中与性能相关的少数陷阱之一。

我很好奇的另一件事是,一个逻辑核心切换权限级别对另一个核心的性能有多大影响。为了测试这一点,您将构建一些工作负载,这些工作负载会限制您选择的前端问题带宽、后端端口、后端 dep 链延迟或后端在中长距离上查找 ILP 的能力(RS 大小或 ROB 大小)。或组合或其他东西。然后比较在内核上运行的测试工作负载与自身的周期/迭代,共享一个具有紧密dec/jnz 线程、4x pause / dec/jnz 工作负载和在 Linux 下进行 ENOSYS 系统调用的syscall 工作负载的内核。也许还有一个int 0x80 工作负载来比较不同的陷阱。


脚注 1:异常处理,例如正常负载下的 #PF。

(题外话,回复:看似无辜的错误指令,而不是陷阱指令,可在解码器中检测为引发异常)。

您等到提交(退休),因为您不想立即启动昂贵的管道刷新,只是发现该指令处于分支未命中(或较早的错误指令)的阴影中并且不应该首先已经运行(使用那个错误的地址)。让快速分支恢复机制抓住它。

这种等待直到退休的策略(以及一个危险的 L1d 缓存,不会将 L1d 命中的负载值压缩为 0,TLB 表示它有效但没有读取权限)是为什么 Meltdown 和 L1TF漏洞利用适用于某些 Intel CPU。 (http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/)。理解 Meltdown 对理解高性能 CPU 中的同步异常处理策略非常有帮助:标记指令并仅在 它达到退休时执行任何操作是一种很好的廉价策略,因为异常非常罕见。

如果后端中的任何微指令检测到待处理的#PF 或其他异常,让执行单元向前端发信号以停止获取/解码/发布显然不值得这么复杂 . (大概是因为这会更紧密地耦合 CPU 中相距很远的部分。)

并且由于在从分支未命中快速恢复期间来自错误路径的指令可能仍在运行中,因此确保您仅在我们认为当前正确执行路径上出现预期错误时停止前端需要更多追踪。后端中的任何 uop 在某一时刻都被认为是在正确的路径上,但当它到达执行单元的末尾时,它可能不再是正确的路径。

如果您没有进行快速恢复,那么也许值得让后端发送“出现问题”信号以停止前端,直到后端实际发生异常或发现异常正确的路径。

使用 SMT(超线程),当线程检测到它当前正在推测导致故障的(可能正确的)路径时,这可以为其他线程留出更多的前端带宽。

所以这个想法可能有一些优点;我想知道是否有任何 CPU 可以做到这一点?

【讨论】:

  • 所以故障是在退休时处理的,这就清除了这一点,而且指令将在受保护的页面中,因此它可能无法推测是否来自用户模式。 '问题/重命名(它已经知道如何停止(部分)序列化指令'你在谈论 FENCE 指令吗?它在这里停止吗?我想它可能会在存储/加载缓冲区中停止,你知道吗,我我开始考虑将 MSROM 中的 int 解码为适当的指令,我想大约是 20 条左右,当你说在预解码时选择哪一个是不切实际的时,我突然明白了......
  • @LewisKelsey:哦,是的,IA32_LSTAR(系统调用入口点)通常会指向设置了管理员位的页面中的地址。但是(来自 Meltdown)我们已经知道 L1d 可以推测性地从这些页面中命中缓存的行中读取实际数据......尽管如此,故意这样做会令人惊讶,即使我们只是在谈论 ping 缓存行以开始一个需求负载,以防 L1i 中冷,而不是实际使用代码获取结果。不过,它也可以探测 iTLB 以启动页面遍历,以防 iTLB 条目是冷的。
  • @LewisKelsey:不,我不是指mfence/sfence 内存屏障,我是指lfence指令-序列化屏障。和cpuid,以及其他真正的序列化指令,使用英特尔的技术术语。 xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/… 摘录了定义和列出它们的英特尔手册的相关部分。 LFENCE 的效果见Are loads and stores the only instructions that gets reordered?
  • @LewisKelsey:是的,int 被微编码了许多微指令。是的,我提到(在这个大答案的某个地方)在预解码中搜索它不值得花费精力,因为稍早停止预解码并不能节省足够的功率,而只是等待解码找到它。是的,解码必须对其进行解码,可能会在 这样做之后停止循环。
  • 解码微码指令只是意味着将“uop”设为指向 MSROM 的指针。它就像一个单一的微指令,但它需要一个完整的微指令缓存行。 MSROM 仅在 uop 到达 IDQ 的 end 时才被访问,其中正常的 uop 只是发布/重命名为 RS 和 ROB。但是微编码的 uops 将 IDQ 重定向到从 MSROM 而不是 IDQ 获取,所以像 rep movsb 这样的东西可以将任意数量的 uops 馈送到后端。 (我忘了我们是否确实知道这个细节,这正是我和 BeeOnRope 提出的最佳猜测理论。)
猜你喜欢
  • 2012-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-22
  • 2011-02-23
  • 2015-04-21
  • 2021-12-11
  • 1970-01-01
相关资源
最近更新 更多