使用监视/等待指令

Cur*_*ous 4 x86 assembly sse intel power-management

我偶然发现了这两个说明- mwaitmonitor https://www.felixcloutier.com/x86/mwait。英特尔手册指出,这些指令用于等待并发多处理器系统中的写入,这让我很好奇,将这些指令添加到ISA时会想到什么类型的用例。

这些指令的语义是什么?是否通过linux将其集成到posix提供的线程库中(例如,在监视单词时线程是否产生)?还是这些只是暂停指令的高级版本?因此,这些指令在超线程上有什么关系?

Had*_*ais 8

在Linux内核中使用monitor/mwait

Linux内核在空闲循环中使用monitor/ mwait指令,当没有计划在内核上运行的可运行任务(空闲任务除外)时,该指令在内核上执行。这些指令在所有Intel x86处理器的空闲循环中都使用,以下情况除外:

  • 处理器不支持指令。从90nm Pentium 4开始的所有Intel Core处理器,所有Intel Atom处理器和所有Xeon Phi处理器均支持这些说明。
  • CPUIDLE子系统被禁用(它是默认启用的,但可以明确的使用被禁用cpuidle.off=1的内核参数)或初始化失败。此外,该处理器不是来自Intel的,还是带有X86_BUG_MONITOR错误的Intel处理器。此错误当前仅在某些Goldmont处理器中存在,在这些处理器中,只能通过IPI唤醒处于低功耗C状态的内核。请参阅:x86:添加解决方法监视器错误
  • mwait 在支持该指令的处理器的BIOS设置中禁用此功能。
  • 使用idle内核参数,该参数采用以下值之一:poll,halt,nomwait。使用此参数时,不使用intel_idle驱动程序(即,使用acpi_idle驱动程序或禁用cpuidle子系统)。在当前的实现中,nomwait实际上与halt相同;两者都使用hlt指令使内核进入睡眠状态(状态C1)。(顺便说一句,以前有第四个选项,称为mwait,但自v3.9-rc1起已被删除,因为它被认为没有用。请参见补丁12。

否则,这些指令用于将任何逻辑内核置于任何受支持的C状态(当然,除了活动状态C0之外)。不管是否启用cpuidle子系统(如上所述除外),使用哪个cpuidle驱动程序以及intel_idle.max_cstate内核参数的值(它指定使用intel_idle还是acpi_idle驱动程序以及最深的C状态),都是这种情况被允许)。

cpuidle驱动程序负责确定哪些功率状态可用于每个处理器,每个功率状态的性能特征(例如,退出等待时间,目标驻留时间和该状态下的功率使用情况)以及如何进入这些状态中的每个状态。

使用intel_idle驱动程序时,可以在此处找到被调用以在驱动程序支持的所有处理器上进入特定状态的函数。它的基本工作原理如下(请注意,此时计时器中断已被禁用):

  • 进入C3状态或更深的状态时,逻辑核心的TLB条目将被刷新,以使核心不会仅为了处理TLB击落而被唤醒。
  • 如果处理器有X86_BUG_CLFLUSH_MONITOR错误,clflush则用于清除由monitor退出睡眠状态的指令所准备的地址范围。据我所知,唯一有此错误的处理器是Intel Xeon Processor 7400(该错误和刷新解决方法已在AAI65勘误中进行了记录)。
  • monitor指令以ecxedx都执行为零。
  • 容易受到MDS攻击的缓冲区将被刷新(如果有)。有关更多信息,请参见this
  • mwait指令以eax包含目标C状态并ecx包含1 的状态执行(即,在中断时退出状态)。

当不使用intel_idle驱动程序时(即,使用了acpi_idle或禁用了cpuidle子系统),该序列是相似的,只是不刷新内核的TLB条目。同样,目标C状态eax始终为C1。

(您可以使用cpupower idle-infocpupower monitor工具确定处理器支持的C状态,哪些cpuidle驱动程序和调控器处于活动状态,以及每个C状态的一些性能和使用特性(每个内核)。)

使用另一种情况mwait是软脱机CPU时。这里使用它的方式类似于我为空闲循环所讨论的方式(请参见代码)。通过将CPU置于最深的可用睡眠状态使其脱机。(但是,一个重要的区别是,必须清空或至少回写包含正在脱机的逻辑核心的物理核心的专用缓存中的所有脏缓存行。之所以这样做(根据线程)是因为缓存一致性没有如果物理核心处于比C1更深的C状态,则无法在专用缓存上工作。有关补丁,请参见此处。)

从休眠状态唤醒系统时,某些处理器可以配置为脱机(例如,当SMT禁用时,所有同级逻辑核心都必须脱机)。除引导处理器(BSP)之外,唤醒系统之前处于休眠状态的脱机核心将仍处于相同的睡眠状态。特别是,仍然可以通过将monitor指令写入相应内核上的存储范围内的地址来唤醒它们。为了确保这些内核都不被过早唤醒(在可以执行地址转换以获取并执行后面的指令之前mwait),BSP 唤醒了所有内核,然后使用hlt指令使其脱机。这不是高效的电源方式(因为hlt仅将核心放在C1中),但在安全性方面是正确的。后来,那应该所有内核进行离线被再次唤醒并投入沉睡使用mwait在一个安全的方式。这是为什么即使在受支持的情况下也要使用hlt代替的示例。mwaitmwait

AMD挖掘机微体系结构和更高版本支持的变种mwait,称为mwaitx,可以配置32位计时器,该计时器以TSC频率计数,并在计时器到期时退出睡眠状态。目前,此指令仅用于实现包括udelay的延迟APIndelay。如果不支持该指令,则通过循环旋转实现延迟,直到TSC寄存器中的值增加所需的周期数为止。该pause指令是类似的,除了睡眠时间是不可配置。

(现代英特尔处理器似乎也支持定时mwait功能,尽管我不认为英特尔为任何当前的处理器都正式记录了此功能。也许这可以解释为什么Linux内核不使用它。)

通常,内核仅按需(即离线时)转换到睡眠C状态之一。即使可以在该程序包的核心上调度可运行线程,也可以将CPU程序包强制处于程序包C状态一段特定的时间百分比。在英特尔Powerclamp驱动程序可用于通过实现这一monitor/ mwait指令。

这些是我所知道的Linux内核中这些指令的全部用法。

的使用monitor/ mwait线程同步

用gcc 9起和内核V5.3-RC1,用户模式的版本mwaitmonitor名为umwaitumonitor,通过暴露_umwait_umonitor内部函数。要使用这些内在函数,请包含immintrin.h标头并使用进行编译-mwaitpkg。仅Tremont支持该指令。umwait它的功能远不如它强大mwait,它的确切行为可以由操作系统通过IA32_UMWAIT_CONTROLMSR控制。glibc当前不使用这些指令。

我认为umwait对于实现自旋锁和条件变量很有用,在这种情况下,您希望线程阻塞直到持有该锁的内存位置被修改(表明该锁已被释放)为止。与之相反mwait,记录了定时器触发的唤醒umwait。当使用来实现同步原语时umwait,请务必记住,从中恢复执行umwait并不一定意味着触发了线程正在等待的条件。umwait可能由于中断,指定的时限到期umwait(可能被OS时限所覆盖)或其他与实现有关的事件而醒来。同样,如果umonitor无法布防原语的地址范围,umwait甚至不会改变C状态。因此,从中唤醒后umwait,线程仍必须执行必要的检查。

umwait当前仅支持两种C状态:C0.1(称为轻量级功率/性能优化状态)和C0.2(称为改进型功率/性能优化状态)。两者都不是睡眠状态。它们基本上是C0的子状态。这类似于pause/ tpause,它将内核保持在C0中。C0.1和C0.2的含义目前尚未记录。我认为这些子状态通过对线程进行去管线化来节省功耗,即不再为该线程获取指令。他们还可以提高另一个同级线程的性能,因为它现在可以使用所有竞争共享资源而无争用。但是,已分区的资源不会重新组合(在过渡到更深的C状态时发生)。

umwait本质上是tpause+的“内存等待”功能,mwaitpause在事务区域中执行时会导致事务中止,例如。在这里值得注意的是,pause延迟是与实现相关的(可能为零),这使得有效利用变得困难。我认为它的唯一优点pause是高度便携。它在130nm Pentium 4和更高版本上受支持,nop并且在不支持它的所有32位和64位Intel和AMD处理器上的表现都类似于。

Knights Landing和Knights Mill提供了一项功能,该功能允许monitormwait包括用户模式在内的任何环中执行。可以通过设置MISC_FEATURE_ENABLES[1]为1 来实现。Linux默认情况下在这些处理器上启用此功能。可以通过传递ring3mwait=disable到内核​​命令行来禁用它(使内核不设置MISC_FEATURE_ENABLES[1]为1,从而将其保持为默认0值)。根据文档:

如果在CPL> 0时或在虚拟8086模式下执行MWAIT,并且如果EAX指示C0或C1以外的C状态,则指令将像EAX指示C状态C1一样操作。

有趣的是,mwait这里可以用来过渡到C1,但是umwait不能。

我不知道KNL / KNM上的此功能是否在任何程序中使用。

可以在这里这里找到有关使用mwaitmonitor进行线程同步的潜力的讨论(两者都很老)。

monitor/的执行特征mwait

双方hltmwait可以用来进入C1。在这种情况下,它们之间的唯一体系结构差异(除了它们是不同的指令)是在SMI中断之后,如果启用了自动暂停重启,则保存的指令指针将指向该hlt指令,而不是该指令之后的指令。因此,如果中断处理程序希望将内核返回睡眠状态,则它可以正常返回而无需执行任何其他操作。根据第3卷第34.10节:

如果重新启动HLT指令,则处理器将生成存储器访问以提取HLT指令(如果它不在内部缓存中),并执行HLT总线事务。此行为导致同一HLT指令发生多个HLT总线事务。

这也适用于AMD处理器。

当逻辑核心进入睡眠状态时,为该逻辑核心进行分区或保留的所有资源都可用于同级核心。至少,这可以提高同级内核的性能(与使用轮询循环相反)。如果另一个同级内核也进入睡眠状态,则整个物理内核都可以进入低功耗状态。如果同一程序包的所有物理内核都进入睡眠状态,则整个程序包(包括非内核)都可以进入低功耗状态。

当发生以下任何事件时,处于睡眠状态(由于执行hltmwait)的内核将转换为C0(活动状态):

  • 发生中断(不必与内核有仿射关系)。
  • 内核监视的地址(通过monitor在有效的WB地址范围上执行)被存储到该地址。
  • 如果时间到,计时器将过期mwait

您可以在英特尔处理器的数据表中找到记录的信息。当然,与mwait和有关的勘误繁多monitor

摘要

??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
?  ?                          ? mwait                 ? mwaitx         ? umwait   ? pause          ? tpause   ? hlt              ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? Wakeup triggers:            ?                       ?                ?          ?                ?          ?                  ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
?  ? WB memory store          ? +                     ? +              ? +        ? –              ? –        ? –                ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
?  ? Unmasked interrupt       ? +                     ? +              ? +        ? ?              ? +        ? +                ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
?  ? Masked interrrupt        ? + (1)                 ? + (1)          ? + (1)    ? ?              ? +        ? –                ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
?  ? Timer                    ? – (2)                 ? + (3)          ? + (4)    ? –              ? + (4)    ? –                ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
?  ? Implementation-dependent ? +                     ? –              ? +        ? –              ? +        ? –                ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? User mode                   ? – (5)                 ? +              ? +        ? +              ? +        ? –                ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? Wakeup IP                   ? Next                  ? Next           ? Next     ? Next           ? Next     ? Next or same (6) ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? Deepest C-state             ? Deepest supported (7) ? C1             ? C0.2 (8) ? C0 (9)         ? C0.2 (8) ? C1               ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? Doesn't abort transaction   ? +                     ? N/A            ? +        ? –              ? +        ? –                ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? Real mode                   ? –                     ? +              ? –        ? –              ? –        ? +                ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? Support                     ? 90nm P4+              ? AMD Excavator+ ? Tremont  ? 130nm P4+ (10) ? Tremont  ? All x86          ?
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

(此ASCII 文字是使用TablesGenerator.com生成的。)

注意:
(1)此行为可通过ecx参数进行配置。
(2)实际上,它至少在最近的微体系结构中确实支持计时器。但是,此功能未记录。
(3)等待时间存储在一个32位的字段,而相比之下,umwaittpause在那里的存储在一个64位字段。
(4)可以在中指定最大等待时间IA32_UMWAIT_CONTROL
(5)在KNL和KNM上,设置MISC_FEATURE_ENABLES[1]为1允许在用户模式下执行指令。
(6)hlt如果启用了自动暂停重启,则指令将在SMI之后重新执行。
(7)在KNL和KNM上,如果MISC_FEATURE_ENABLES[1]为1,则最深的C状态为C1。
(8)如果IF IA32_UMWAIT_CONTROL[0]为1,则最深的C状态为C0.1。
(9)据我了解。
(10)nop在所有不支持它的32位和64位Intel和AMD处理器上的行为相同。


Bre*_*dan 6

这些指令的语义是什么?

一般的想法是,不用设置轮询循环(例如“ while( *foo == 0) {}”),而是设置监视器(使用monitor),然后检查条件,然后(如果条件尚未发生)则等待监视器被触发(使用mwait)。这样,在等待条件变化时,CPU可以消耗更少的功率(和/或让同一内核中的其他逻辑处理器更好地运行)。

然而; 可能存在误报(写入同一高速缓存行中的其他内容)和导致mwait停止等待的其他内容(IRQ)。因此,您仍然需要循环检查条件;所以整个事情都像(例如)结束monitor(foo); while(*foo == 0) { mwait(); }

是否通过linux将其集成到posix提供的线程库中(例如,在监视单词时线程是否产生)?

这些指令通常不能在用户空间中使用(要求CPL = 0)。注意:有一个建议的扩展,以允许在用户空间中使用Monitor / Mwait(的一个版本),但是我不确定是否曾经实现(还好吗?)。

然而; 当没有想要CPU的任务时,它们通常在内核的调度程序中使用(监视想要CPU的空列表并在将任务添加到列表中时唤醒CPU)。这样,它最终可能会被更高级别的用户空间事物(例如pthread_condvars)使用。

注意:很久以前(大约5年了?),我记得曾经有一些关于使用monitor/ mwait作为自旋锁的研究(在内核中)。结论是CPU唤醒时间太长,不值得这样做。从那以后,我不确定是否有任何变化。

还是这些只是暂停指令的高级版本?

pause指令是非常不同-它告诉CPU不积极(推测)执行后来指令(不要告诉CPU等待/没有执行指令)。它在轮询循环中也很有用,但出于不同的原因。

因此,这些指令在超线程上有什么关系?

如果在核心一个逻辑CPU是什么都不做(例如mwaithlt),然后在内核的另一个逻辑CPU可以使用整个核心更快地执行的东西。

如果内核中的一个逻辑CPU做得更少(因为pause告诉CPU不要在推测性执行中如此激进),则内核中的另一个逻辑CPU可以使用更多的内核来更快地执行任务。