管道中的软件中断会发生什么?

问题描述 投票:3回答:2

看完之后:

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

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

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

我想知道管道中的软件中断(INT 0xX)会发生什么,首先,它们何时被检测到?它们是否可能在预编码阶段被检测到?在指令队列中?在解码阶段?或者他们到达后端并立即完成(不要进入预订站),轮流退休,退休阶段发现这是INT指令(似乎很浪费)。

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

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

x86 intel pipeline interrupt-handling trap
2个回答
2
投票

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


但是,是的,他的答案没有详细说明管道如何优化同步int陷阱指令,我们在解码时会知道它总是会导致异常。陷阱指令在整个指令组合中也非常少见,因此优化它们并不会为您节省大量功率;只做一件容易的事情是值得的。

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


我假设像lfence一样,CPU没有推测过去的陷阱指令。你是对的,没有必要让那些uops在管道中,因为在int之后的任何东西肯定会被冲洗。

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

误报命中陷阱指令可能是非常罕见的,如果前端看到陷阱指令,如果前端足够聪明,那么从IDT开始加载或预取syscall入口点是值得的。处理这一切。但它可能不是。将奇特的东西留给微码是有意义的,以限制前端的复杂性。陷阱是罕见的,即使在syscall繁重的工作量。在整个用户/内核屏障上更换大块的批处理工作是一件好事,因为在Spectre之后便宜的syscall是非常难的...


所以最迟在发布/重命名中检测到一个陷阱(它已经知道如何停止(部分)序列化指令),并且在int之前没有进一步的uops被分配到无序后端。退休,正在采取例外。

但是在解码中检测它似乎很可能,并且不会进一步解码超过肯定会出现异常的指令。 (而且我们不知道下一步要去哪里。)解码器阶段确实知道如何停止,例如非法指令陷阱。

假设它在预编码处被拾取

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

在HW中构建以更深入地搜索陷阱指令将花费比预解码中更多的功率。 (请记住,陷阱非常罕见,早期检测它们大多只能节省电量,所以你不能花费更多的力量来寻找它们,而不是通过在将陷阱传递到解码器后停止预解码来节省它们。

您需要解码int,以便其微码可以执行并让CPU再次启动运行中断处理程序,但理论上,您可以在通过后在循环中预解码停顿。

例如,这是常规解码器,其中识别出分支预测遗漏的跳转指令,因此主要解码阶段通过不再进一步处理陷阱更有意义。


超线程

当你发现一个失速时,你不只是为前端供电。你让其他逻辑线程拥有所有周期。

超线程使得前端无需后端的帮助即可从IDT指向的内存中获取内容。如果另一个线程没有停止,并且在该线程对其陷阱进行排序时可以从额外的前端带宽中受益,则CPU正在做有用的工作。

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

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


Footnote 1: Exception handling, like #PF on a normal load.

(关闭主题,重新:无辜的查看指令,错误,不捕获可在解码器中检测为提高异常的指令)。

你等到提交(退休),因为你不想马上开始一个昂贵的管道冲洗,只是发现这条指令是在分支未命中(或早期的故障指令)的阴影下,不应该运行(首先是那个糟糕的地址。让快速的分支恢复机制抓住它。

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

如果后端中的任何uop检测到挂起的#PF或其他异常,则执行单元发送信号回到前端以停止提取/解码/问题显然不值得复杂。 (大概是因为那会更紧密地耦合CPU的部分,否则相距很远。)

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

如果你没有进行快速恢复,那么可能值得让后端发送“有问题”的信号来阻止前端,直到后端实际发生异常,或者发现正确的路径。

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

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


3
投票

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

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

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

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

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.