x86 - 流水线中的软件中断会如何处理?

标签 x86 intel pipeline interrupt-handling

阅读以下内容后:

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

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

相反,异常(如页面错误)会标记受影响的指令。当该指令即将提交时,此时将清除异常之后的所有后续指令,并重定向指令提取。

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

假设它是在预解码时拾取的,必须有一种发信号通知IFU停止获取指令或实际上对其进行时钟/电源门控的方法,或者如果它是在指令队列中拾取的,则是一种在队列中先清除指令的方法。这样就必须有一种向某种逻辑(“控制单元”)发出信号的方式,例如为软件中断生成索引(索引到IDT,检查DPL> = CPL> =段RPL等),是幼稚的的建议,但是如果有人对此过程有更好的了解,那就太好了。

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

最佳答案

Andy @Krazy Glew的话是关于在执行“正常”指令期间发现的同步异常,例如mov eax, [rdi]提高了#PF,如果事实证明RDI指向未映射的页面。1您希望不要出错,因此可以推迟直到退休,以防分支错误预测或较早的异常的情况下,才做任何事情。

但是,是的,他的答案没有详细说明流水线如何针对同步int陷阱指令进行优化,而我们在解码时知道这将始终导致异常。陷阱指令在整个指令组合中也很少见,因此对其进行优化并不能节省很多精力。只有做简单的事情才值得。
如Andy所说,当前的CPU不会重命名特权级别,因此无法推测为中断/异常处理程序,因此在看到intsyscall后暂停获取/解码绝对是明智的选择。我只是要编写int或“陷阱指令”,但是syscall / sysenter / sysret / iret和其他特权更改的“分支”指令也是如此。而1-byte versions of int 类似于int3(0xcc)和int1(0xf1)。有条件的溢出陷阱into很有趣;对于在无陷阱情况下的非可怕性能,可能会假设它没有被陷阱捕获。 (当然,还有用于VMX扩展的vmcall和东西,可能还有SGX EENTER,也许还有其他东西。但是就停滞管道而言,我猜所有陷阱指令都是相同的,除了条件into之外)

我假设像lfence一样,CPU不会推测陷阱指令。 没错,将这些uops放入管道中是没有意义的,因为int之后的所有内容肯定都被刷新了。
如果要从IVT(实模式中断向量表)或IDT(中断描述符表)中获取任何内容,则IDK,以在int指令在后端变为非推测性之前获取int处理程序的地址。可能吧(有些陷阱指令(例如syscall)使用MSR设置处理程序地址,因此从那里开始获取代码可能会很有用,特别是如果它提早触发了L1i未命中。这必须权衡是否看到int和其他在分支未命中后将指令捕获在错误的路径上。)
误认为命中陷阱指令的可能性可能非常罕见,只要前端看到陷阱指令,就可以从IDT开始加载或一旦前端看到陷阱指令就预取syscall入口点,这是值得的。处理所有这一切。但这可能不是。将花哨的东西留给微代码可以限制前端的复杂性。即使在syscall繁重的工作负载中,陷阱也很少见。批量工作以跨用户/内核屏障传递更大的块是一件好事,因为廉价的syscall在Spectre之后非常困难...

因此,最迟将在issue / rename(已经知道如何暂停(部分)序列化指令)中检测到一个陷阱,并且除非有任何问题,否则不会将更多的uops分配给无序的后端。 int已停用,并且正在采取异常。
但是似乎有可能在解码中检测到它,而不是在绝对要带异常的指令之后再解码。 (而且我们不知道下一步要去哪里。)解码器阶段确实知道如何停顿,例如用于非法指令陷阱。

假设它是在预解码中拾取的

这可能不切实际,直到完全解码,您才知道它是int。预解码只是在Intel CPU上进行指令长度查找。我假设intsyscall的操作码只是长度相同的许多操作码中的两个。
在硬件中构建以更深入地搜索陷阱指令将比在预解码中花费更多动力。 (请记住,陷阱非常罕见,并且尽早检测到陷阱通常只会节省电量,因此,在将陷阱传递到解码器后停止预解码,您所花费的精力无法比节省更多的精力来寻找它们。
您需要对int进行解码,以便其微代码可以执行并再次使CPU开始运行中断处理程序,但是是的,从理论上讲,您可以在通过后在循环中进行预解码停止。
例如,在常规解码器中,会识别出分支预测丢失的跳转指令,因此,对于主解码阶段而言,无需进一步处理陷阱即可。

超线程
发现停顿时,您不仅可以对前端进行电源门控。您让另一个逻辑线程拥有所有周期。
超线程使前端在没有后端帮助的情况下开始从IDT指向的内存中获取数据的价值降低。如果另一个线程没有停止,并且可以在清理该陷阱的同时受益于额外的前端带宽,则CPU会做有用的工作。
我当然不会排除从SYSCALL入口点获取代码的情况,因为该地址位于MSR中,并且它是在某些工作负载中与性能相关的少数陷阱之一。
我很好奇的另一件事是,一个逻辑核心切换特权级别对任何性能的影响是否会对另一个核心的性能造成多大的影响。为了测试这一点,您需要构建一些工作负载,这些工作负载会限制您选择的前端问题带宽,后端端口,后端dep链延迟或后端在中长距离上查找ILP的能力(RS大小或ROB大小)。或组合或其他东西。然后,将在内核上运行的该测试工作负载的周期/迭代与自身进行比较,将内核与紧密的dec/jnz线程,4x pause / dec/jnz工作负载和syscall工作负载共享,从而在Linux下进行ENOSYS系统调用。也许还有一个int 0x80工作量来比较不同的陷阱。

脚注1:异常处理,如正常负载下的#PF。
(离题,re:无辜的看似出错的指令,而不是陷阱,这些指令可以在解码器中检测为引发异常)。
您一直等到提交(退休),因为您不想立即开始进行昂贵的管道刷新,而只是发现该指令在分支未命中(或更早的故障指令)的阴影下并且不应该运行(首先是那个错误的地址)让快速的分支恢复机制抓住它。
这种等待至退休的策略(以及危险的L1d缓存不会将L1d命中的负载值压缩为0,而TLB表示有效,但没有读取权限)是Meltdown和L1TF利用在某些Intel上起作用的关键CPU。 (http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/)。了解Meltdown对理解高性能CPU中的同步异常处理策略非常有帮助:标记指令并仅在退出后才执行任何操作,这是一种很好的廉价策略,因为异常非常罕见。
如果后端中的任何uop检测到未决的#PF或其他异常,则让执行单元发信号给前端以停止获取/解码/发出信号显然不值得那么复杂。 (大概是因为这样会更紧密地耦合CPU的其他部分,否则它们之间的距离会很远。)
而且由于在从分支未命中快速恢复期间,错误路径中的指令可能仍在执行中,并且确保仅针对我们认为当前正确的执行路径上的预期错误停止前端,将需要更多跟踪。后端中的所有uop都曾经被认为处于正确的路径上,但是到执行单元的末端时可能不再存在。
如果您没有进行快速恢复,那么后端就应该发送一个“东西不对劲”的信号来阻塞前端,直到后端实际采取异常措施或发现正确的路径,这也许是值得的。
使用SMT(超线程),当一个线程检测到当前正在推测导致故障的(可能正确的)路径时,这可能为其他线程留出更多的前端带宽。
因此,这种想法可能有其优点。我想知道是否有CPU这样做?

关于x86 - 流水线中的软件中断会如何处理?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54427842/

相关文章:

assembly - 如何通过AND运算得到SF = 1?

assembly - 通用寄存器 - 订购

x86 - x86 中的 .data 和 .text 是什么?

powershell - 使用 Powershell 解析 RSTCLI 的卷状态

assembly - NASM 中的 .bss 部分中声明的变量是否会加载到进程的数据部分而不是进程的 bss 部分?

Intel intrinsics 支持 Atom cloverview 处理器

intel - 将多个 _mm128 与单个条目 _mm256 相乘

r - 基于重要性的变量缩减

gitlab - 如何更改管道徽章名称

c# - 将 System.IO.Pipelines 与 Stream 一起使用