【问题标题】:Uses of the monitor/mwait instructionsmonitor/mwait 指令的使用
【发布时间】:2019-12-19 15:55:04
【问题描述】:

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

这些指令的语义是什么?这是否通过 linux 集成到 posix 提供的线程库中(例如,线程在监视单词时是否会产生)?或者这些只是暂停指令的更高级版本?那么,这些指令与超线程有什么关系呢?

【问题讨论】:

  • 有趣的事实:另一个主要用途是将 CPU 置于睡眠状态(C 状态),由操作系统选择睡眠深度。 (与刚刚进入最浅睡眠的hlt 相比)。如果 Linux 内核使用特权监视器/mwait 进行实际监视,则 IDK;但是用户空间版本 umonitor / umwait 最近被添加到 Tremont(下一代 Goldmont atom)en.wikichip.org/wiki/intel/microarchitectures/tremont 并参见 phoronix.com/…

标签: assembly x86 intel sse power-management


【解决方案1】:

这些指令的语义是什么?

一般的想法是,不是有一个轮询循环(例如“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 可以使用更多的核心来更快地执行任务。

【讨论】:

  • 用户空间umonitor/umwait 将在“WAITPKG”CPU 功能中加入 Tremont (atom)。连同tpausepause,直到 TSC 截止日期。
  • @PeterCordes:谢谢(我不记得具体细节了)。我希望内核有一种方法可以在这些指令到达时禁用这些指令——我可以看到潜在的安全灾难(特别是对于第一次实现,“出牙问题”的风险高于平均水平)。 ;-)
  • AFAIK,mwait 是让 CPU 进入深度睡眠(C 状态)的唯一方法。所以这是在无事可做时在调度程序中使用它的另一个巨大好处。
  • 关于安全性的有趣点,从英特尔手册来看,如果设置了 CR4.TSD 位(我认为时间戳禁用),它们似乎只会出错(在用户空间中)。因此,除非在其他地方有其他记录来禁用该功能,否则如果出现任何问题,我们都会等待微码更新。 umwaittpause 可以请求的最深睡眠远没有 mwait 那样深,因此他们预料到了 DOS / 打破实时问题。 (另外,phoronix.com/… 有一些关于 Linux 准备的消息和链接)
  • 这可能只会为您提供物理地址而没有多大帮助,除非有一些别名可以让您发现一些地址位......但我注意到他们首先在 Tremont 推出了这个;其中大多数是客户端设备,而不是托管彼此不信任的虚拟机。许多 Atom 驱动的设备都是 NAS,其威胁模型可能信任设备上运行的所有代码,除了权限提升。
【解决方案2】:

monitor/mwait 在 Linux 内核中的使用

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

  • 处理器不支持指令。从 90nm Pentium 4 开始的所有 Intel Core 处理器、所有 Intel Atom 处理器和所有 Xeon Phi 处理器都支持这些指令。
  • cpuidle subsystem 被禁用(默认启用,但可以使用cpuidle.off=1 内核参数显式禁用)或初始化失败。此外,处理器不是来自英特尔,或者是带有X86_BUG_MONITOR 错误的英特尔处理器。此错误目前仅存在于某些 Goldmont 处理器中,其中低功耗 C 状态的内核只能通过 IPI 唤醒。请参阅:x86: add workaround monitor bug
  • 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 驱动程序时,可以在here 找到该驱动程序支持的所有处理器上进入特定状态的调用函数。它基本上是这样工作的(注意此时定时器中断已经被禁用):

  • 当进入 C3 状态或更深的状态时,逻辑内核的 TLB 条目会被刷新,这样内核就不会仅仅为了处理 TLB 击落而被唤醒。
  • 如果处理器有X86_BUG_CLFLUSH_MONITOR 错误,clflush 用于刷新由用于退出睡眠状态的monitor 指令武装的地址范围。据我所知,唯一存在此错误的处理器是 Intel Xeon 处理器 7400(错误和刷新解决方法记录在 AAI65 勘误表中)。
  • monitor 指令在 ecxedx 均为零的情况下执行。
  • 易受 MDS 攻击的缓冲区被刷新(如果有)。如需更多信息,请参阅this
  • mwait 指令执行时,eax 包含目标 C 状态,ecx 包含 1(即,在中断时退出状态)。

当不使用 intel_idle 驱动程序时(即使用 acpi_idle 或禁用 cpuidle 子系统),sequence 类似,除了内核的 TLB 条目不刷新。 eax 中的目标 C 状态也始终是 C1。

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

使用mwait 的另一种情况是软脱机CPU 时。它在这里的使用方式类似于我讨论过的空闲循环(参见code)。 CPU 通过将其置于最深的可用睡眠状态而使其脱机。 (但一个重要的区别是,物理内核的私有缓存中的所有脏缓存行(包含正在脱机的逻辑核心)必须被刷新或至少写回。原因(根据this 线程)是缓存如果物理内核的 C 状态比 C1 更深,则一致性在私有缓存上不起作用。相关补丁可以在 here 找到。)

当从休眠状态唤醒系统时,一些处理器可能被配置为离线(例如,当 SMT 被禁用时,所有同级逻辑内核都必须离线)。休眠前处于离线状态的内核在唤醒系统时仍将处于相同的睡眠状态,但引导处理器 (BSP) 除外。特别是,它们仍然可以通过写入内存范围内的地址来唤醒它们,这些地址在各自的内核上配置了monitor 指令。为了确保这些内核都不会被过早唤醒(在可以执行地址转换以获取并执行mwait 之后的指令之前),BSP wakes up all the cores,然后使用 hlt 指令将它们离线反而。这在功率方面效率不高(因为hlt 仅将核心放在 C1 中),但在安全正确性方面是安全的。稍后,所有应该离线的核心都是woken up again and put to deepest sleep,以安全的方式使用mwait。这是一个示例,说明您为什么要使用 hlt 而不是 mwait,即使支持 mwait

AMD Excavator 微架构及更高版本支持mwait 的变体,称为mwaitx,可配置32 位定时器,以TSC 频率计数并在定时器到期时退出睡眠状态。目前,该指令仅用于implement the delay APIs,包括udelayndelay。如果不支持该指令,则通过循环旋转来实现延迟,直到 TSC 寄存器中的值增加大约所需的周期数。 pause 指令类似,只是睡眠时间不可配置。

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

通常,核心仅按需转换到睡眠 C 状态之一,即当它离线时。可以强制 CPU 包在特定百分比的时间内处于包 C 状态,即使可以在该包的核心上调度可运行的线程也是如此。 Intel Powerclamp driver 可以通过monitor/mwait 指令来实现。

这些是我所知道的 Linux 内核中这些指令的所有用途。

使用monitor/mwait 进行线程同步

从 gcc 9 和内核 v5.3-rc1 开始,mwaitmonitor 的用户模式版本,称为 umwaitumonitor,通过 _umwait_umonitor 内在函数公开.要使用这些内在函数,请包含 immintrin.h 标头并使用 -mwaitpkg 进行编译。当前没有处理器支持这些指令(Tremont 中的 CPUID 信息是正确的,而当前的 Intel 文档对此有误)。第一个支持这些指令的微架构可能是 Sapphire Rapids。 umwait 远没有mwait 强大,并且它的确切行为可以通过IA32_UMWAIT_CONTROL MSR 由操作系统控制。 glibc 目前不使用这些指令。

我认为umwait 对于实现自旋锁和条件变量很有用,您希望线程阻塞,直到持有锁的内存位置被修改(表明锁已被释放)。与mwait 相比,定时器触发的唤醒记录在umwait 中。当使用umwait 实现同步原语时,重要的是要记住从umwait 恢复执行并不一定意味着触发了线程等待的条件。 umwait 可能由于中断、umwait 指定的时间限制到期(可能被操作系统时间限制覆盖)或其他与实现相关的事件而唤醒。此外,如果umonitor 未能武装原语的地址范围,umwait 甚至不会更改 C 状态。这就是为什么从umwait 唤醒后,线程仍必须执行必要的检查。

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

umwait 本质上是tpause + mwait 的“内存等待”功能 + 当在事务区域中执行时,它会导致事务中止,如pause。这里值得注意的是pause 延迟是依赖于实现的(它可能是零),这使它成为hard to use effectively。我认为pause 的唯一优势是它的高度便携性;它在 130nm Pentium 4 及更高版本上受支持,它在所有不支持它的 32 位和 64 位 Intel 和 AMD 处理器上的行为类似于 nop

Knights Landing 和 Knights Mill 提供 a feature that allows monitor and mwait to be executed in any ring 包括用户模式。这可以通过将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 进行线程同步的潜力的一些讨论可以在herehere 找到(这两个都非常老了)。

monitor/mwait的执行特征

hltmwait都可以用来进入C1。在这种情况下,它们之间唯一的架构区别(除了它们是不同的指令)是在 SMI 中断之后,如果启用了自动暂停重启,则保存的指令指针指向 hlt 指令,而不是它后面的指令。因此,如果中断处理程序想要将内核返回到睡眠状态,它可以正常返回,而无需做任何额外的事情。根据第 3 卷 34.10:

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

这也适用于 AMD 处理器。

当一个逻辑核心进入睡眠状态时,所有为其划分或保留的资源都可供同级核心使用。至少,这可以提高兄弟内核的性能(与使用轮询循环相比)。如果其他同级内核也进入睡眠状态,则整个物理内核可以进入低功耗状态。如果同一个包的所有物理核心都进入休眠状态,则整个包(包括非核心)可以进入低功耗状态。

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

  • 发生中断(不必仿射到内核)。
  • 内核监控的地址(通过在有效的 WB 地址范围上执行 monitor)存储到。
  • 如果是定时mwait,计时器就会到期。

您可以在英特尔处理器的数据表中找到该信息。当然,还有大量与mwaitmonitor 相关的勘误表。

总结

╔══╦═════════════════════════════════════╦═══════════════════════╦════════════════╦═════════════════╦════════════════╦═════════════════╦══════════════════╗
║  ║                                     ║ mwait                 ║ mwaitx         ║ umwait          ║ pause          ║ tpause          ║ hlt              ║
╠══╩═════════════════════════════════════╬═══════════════════════╬════════════════╬═════════════════╬════════════════╬═════════════════╬══════════════════╣
║ Wakeup triggers:                       ║                       ║                ║                 ║                ║                 ║                  ║
╠══╦═════════════════════════════════════╬═══════════════════════╬════════════════╬═════════════════╬════════════════╬═════════════════╬══════════════════╣
║  ║ WB store from a processor agent     ║ +                     ║ +              ║ +               ║ –              ║ –               ║ –                ║
╠══╬═════════════════════════════════════╬═══════════════════════╬════════════════╬═════════════════╬════════════════╬═════════════════╬══════════════════╣
║  ║ WB store from a non-processor agent ║ No guarantee          ║ No guarantee   ║ +               ║ –              ║ –               ║ –                ║
╠══╬═════════════════════════════════════╬═══════════════════════╬════════════════╬═════════════════╬════════════════╬═════════════════╬══════════════════╣
║  ║ Non-WB stores                       ║ No guarantee          ║ No guarantee   ║ No guarantee    ║ –              ║ –               ║ –                ║
╠══╬═════════════════════════════════════╬═══════════════════════╬════════════════╬═════════════════╬════════════════╬═════════════════╬══════════════════╣
║  ║ 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+ ║ Atom Tremont,   ║ 130nm P4+ (10) ║ Atom Tremont,   ║ All x86          ║
║                                        ║                       ║                ║ Alder Lake,     ║                ║ Alder Lake,     ║                  ║
║                                        ║                       ║                ║ Sapphire Rapids ║                ║ Sapphire Rapids ║                  ║
╚════════════════════════════════════════╩═══════════════════════╩════════════════╩═════════════════╩════════════════╩═════════════════╩══════════════════╝

(此 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) 如果IA32_UMWAIT_CONTROL[0]为1,则最深的C-state为C0.1。
(9) 据我了解。
(10) 在不支持它的所有 32 位和 64 位 Intel 和 AMD 处理器上表现为nop

【讨论】:

  • felixcloutier.com/x86/hlt 表示 CS:EIP 在中断返回后指向 HLT 指令之后的指令。这就是为什么你要么cli/hlt(假设没有 NMI)要么将hlt 放在玩具引导加载程序末尾的循环中,作为jmp $ 的替代方案。
  • @PeterCordes felixcloutier.com/x86/mwait 说“与 HLT 指令不同,MWAIT 指令不支持在处理 SMI 后在 MWAIT 指令处重新启动。” UMWAIT 也是如此。也许这是特定于 SMI 的?
  • @PeterCordes 是的,请参阅第 3 卷第 34.10 节。
  • 是的,我认为这是关于 SMI,而不是常规中断。我对 SMI 和 SMM 或 mwait 的了解还不够,无法得出更多结论。
  • 有趣的更新。我想知道pause 是否有任何可以“唤醒”的东西。我猜它不会延迟中断处理。它可能只是让前端休眠固定数量的周期,并为后端或发布阶段提供一个特殊的 uop,以防止内存顺序错误推测
猜你喜欢
  • 1970-01-01
  • 2022-07-08
  • 2011-06-28
  • 2017-12-13
  • 2017-02-10
  • 2014-05-02
  • 2019-06-11
  • 2019-12-18
  • 2021-09-30
相关资源
最近更新 更多