【发布时间】:2017-12-13 03:05:48
【问题描述】:
我正在探索MONITOR 指令(或等效的内在函数_mm_monitor)的用法。虽然我找到了描述它们的文献,但我找不到任何关于如何使用它的具体示例/示例。
谁能分享如何在驱动程序中使用此指令/内在函数的示例?本质上,我想用它来观察内存范围。
【问题讨论】:
标签: x86 driver cpu-architecture smp
我正在探索MONITOR 指令(或等效的内在函数_mm_monitor)的用法。虽然我找到了描述它们的文献,但我找不到任何关于如何使用它的具体示例/示例。
谁能分享如何在驱动程序中使用此指令/内在函数的示例?本质上,我想用它来观察内存范围。
【问题讨论】:
标签: x86 driver cpu-architecture smp
monitor 指令使用RAX/EAX/AX 中指定的地址武装地址监控硬件。来自英特尔的引述
监视器的状态由指令mwait使用。
使用的有效地址大小(16、32 或 64 位)取决于编码指令的有效地址大小(即可以用 67h 前缀覆盖它,默认情况下它与代码大小相同)。
rax/eax/ax 中给出的地址是逻辑地址的偏移部分,用于计算用于启动监视器的线性地址。
段部分默认为ds,可以应用段覆盖前缀来更改段。
作为用于监视器的线性地址,分页不会影响监视。
monitor(和mwait)指令的可用性由位CPUID.01H:ECX.MONITOR[bit 3]1指示。
这是一个特权指令,但英特尔声称:
指令在大于 0 的级别有条件地可用。
检测这种情况的建议方法是尝试执行monitor 并处理最终的#UD 异常(以操作系统的自定义方式将其报告给用户程序)。
监控的地址范围必须是可写回缓存的。
由于涉及高速缓存和高速缓存一致性子系统,地址范围的大小以最小和最大大小给出。
CPUID.01H:EAX[bit 15: 0] 给出最小范围大小。这是硬件监视器监视的区域的长度。
但是,缓存一致性流量可能适用于更大尺寸的“块”(行),并且如果后者包含在前者中,则与受监控区域相邻的写入仍会触发它。
这会产生最大范围大小,可以在 CPUID.01H:EBX[bit 15:0] 中找到。
要正确使用monitor,请确保监控的数据结构适合最小范围大小,但还要确保没有代理写入它旁边的地址直到最大范围大小。
例如,如果最小范围大小是 8 个字节,最大大小是 16 个字节,请确保观察到的结构适合 8 个字节,但要再填充 8 个字节以达到总共 16 个字节,这样就不会从出现第 8 到第 16 个字节。
在单个集群系统中,以上两个值相等。我的都是 64 字节。
BIOS 负责在多集群系统中报告IA32_MONITOR_FILTER_LINE_SIZE 中的高速缓存一致性行大小。
出于指令顺序和访问权限的目的,monitor 是一个负载。
monitor 允许程序员指定提示和扩展。
扩展名在ecx 中指定,而提示在edx 中。
不受支持的扩展会引发 #GP 异常,不受支持的提示将被忽略。
我不知道monitor 的任何扩展或提示,英特尔手册报告
奔腾 4 处理器(系列 15,型号 3),未定义扩展或提示。
我相信这条线总体上是正确的,它只是其中有一个过时的处理器型号。
此外,monitor 的伪代码报告了 #GP If ECX ≠ 0.
在不检查其状态的情况下启动监视器(使用mwait)不会造成任何伤害。
内在函数是void _mm_monitor(void const *p, unsigned extensions,unsigned hints)。
监视器布防后,可以通过不同的条件触发:
- 外部中断:NMI、SMM、INIT、BINIT、MCERR
- 故障、中止,包括机器检查
- 架构 TLB 失效,包括写入 CR0、CR3、CR4 和某些 MSR 写入
- 由于快速系统调用和远调用导致的自愿转换
- 屏蔽中断(如果启用)
- 在受监控的地址范围内写入
监视器的状态对程序员是不可见的,但可以用mwait 进行测试。mwait 进入实现定义的低功耗状态,直到监视器处于触发状态。
如果监视器未进入待命状态或者它已经被触发mwait 是nop,否则它会使处理器停止执行指令,直到监视器被触发。
mwait 也可以被赋予扩展和提示。
扩展在ecx 中设置,在eax 中设置提示。
在撰写本文时,唯一的扩展是:
位 0 即使被屏蔽(例如,即使 EFLAGS.IF=0),也将中断视为中断事件。仅当 CPUID.05H:ECX[bit 1] = 1.
Bits 31-1 保留
提示让程序员指定实现定义的低功耗模式。
位 3:0 一个 C 状态中的子 C 状态,由位 [7:4] 指示
位 7:4 目标 C 状态
值为 0 表示 C1; 1 表示 C2 以此类推
01111B 的值表示 C0
注意:MWAIT 扩展的目标 C 状态是特定于处理器的 C 状态,而不是 ACPI C 状态
C 模式的子状态数(因此是可用性)在 CPUID.05h.EDX 中给出:
位 03 - 00:使用 MWAIT 支持的 C0* 子 C 状态的数量。
位 07 - 04:使用 MWAIT 支持的 C1* 子 C 状态的数量。
位 11 - 08:使用 MWAIT 支持的 C2* 子 C 状态的数量。
位 15 - 12:使用 MWAIT 支持的 C3* 子 C 状态的数量。
位 19 - 16:使用 MWAIT 支持的 C4* 子 C 状态的数量。
位 23 - 20:使用 MWAIT 支持的 C5* 子 C 状态的数量。
位 27 - 24:使用 MWAIT 支持的 C6* 子 C 状态的数量。
位 31 - 28:使用 MWAIT 支持的 C7* 子 C 状态的数量。
请注意,将 CPU 置于高于 C1 的状态也会禁用其他线程,因此触发监视器的写入必须来自其他代理。
内在函数是void _mm_mwait(unsigned extensions, unsigned hints)。
monitor/mwait 机制是为了帮助线程之间的同步而引入的,它不太适合监视对内存范围的访问,因为触发条件包括频繁发生的事件。
在 mwait 之后始终强制检查是否已写入受监视范围。
有一个example here,其模式如下:
monitor/mwait 对。mwait“返回”,被监视的结构值与1(发生写入)比较,如果不相等则执行跳回2。一些示例,未经测试伪代码可能是:
struct MonitoredType
{
int (*event)(struct MonitoredType const* m); /*Return 0 to keep monitoring*/
struct AnyType data; /*Less, in size, than MIN_MONITOR_RANGE*/
char padding[MAX_MONITOR_RANGE - sizeof(AnyType)];
};
void wait_for_write(struct MonitoredType const* m)
{
/* This may miss a write if it happens before MONITOR, beware of race conditions if necessary */
do
{
_mm_monitor(&m->data, 0, 0);
_mm_mwait(0, 0);
} while ( ! m->event(m));
}
必须注意确保mwait 的退出条件是写入事件,而不是其他事件之一。
这就是函数指针event 的原因。
为了监控对线性地址的写入/读取,另一种方法是使用调试寄存器。
请参阅Intel manual 3 的第 17 章并检查您的操作系统文档以正确使用这些寄存器。
1 含义:执行cpuid,将eax设置为01h,然后测试ecx的第3位。请注意IA32_MISC_ENABLE 允许操作系统或固件禁用monitor/mwait。
【讨论】: