我已经看到了相关问题,包括here和here,但似乎唯一提到的序列化指令
rdtsc
是cpuid
。
不幸的是,
cpuid
在我的系统上大约需要1000个周期,所以我想知道是否有人知道更便宜的(更少的周期并且不读取或写入内存)序列化指令?
我看了
iret
,但这似乎改变了控制流程,这也是不可取的。
我实际上已经查看了 Alex 关于
rdtscp
的回答中链接的白皮书,但它说:
RDTSCP 指令会等待,直到所有先前的指令都已完成 在读取计数器之前执行。 然而,随后 指令可以在读操作之前开始执行 执行了。
第二点似乎让它不太理想。
订购
rdtsc
wrt。其他说明,如果您不需要等待存储缓冲区耗尽,则 lfence
就足够了。自从一直使用 Intel,自从 Spectre 缓解在 AMD 以来。请参阅rdtsc乱序执行的解决方案?
rdtscp
也保证可以订购。较早的指令(但不是较晚的指令;实际上,它的微编码可能非常类似于 lfence
;rdtsc
按此顺序,加上用处理器 ID 写入 ECX 的微指令。)它不是 x86 序列化指令,甚至不会耗尽存储缓冲区。 (无论如何,你不一定想要计时。)如果你想要的话,你可以 mfence; rdtscp
或 lock or byte [rsp], 0 ; rdtscp
,或者 rdtscp; lfence
,如果你想确保它的几个微指令不能与以后的东西一起重新排序。
另请参阅此问答,了解有关 TSC 的更多一般信息,它是固定频率,而不是 CPU 周期。
回答标题中关于 x86 技术术语意义上的“序列化指令”的问题,
Alder Lake(和 Sapphire Rapids)以及后来的
serialize
,它正是这样做的,仅此而已。
lfence
序列化指令执行(耗尽 ROB 但不存储缓冲区或前端):请参阅
在虚拟机中,
cpuid
是有保证的 vmexit,因此速度很慢。推送 RSP、RFLAGS、CS 和 RIP 并运行 iret
指令可能会更快。我没有仔细检查 iret 弹出的内容,因此可能不完全正确。
交叉修改代码是一种正确的序列化指令与
mfence
;lfence
之类的指令相比很重要的情况。在获取加载看到表明新代码存在的发布存储后,您需要运行序列化指令。英特尔第 3 卷手册第 8.1.3 节保证了交叉修改代码的安全性。
我认为这可以确保前端尚未获取旧代码。因此,序列化指令可能会完全破坏管道,或者如果对管道中的最近指令有足够的跟踪来窥探 L1i 失效,则可以执行等效操作。 (这种额外的窥探可能不值得,因为序列化指令很少见。无论如何,都需要跟踪来处理self修改代码,窥探存储地址是否靠近任何正在运行的指令。)
mfence
(或lock or byte [rsp],0
)+lfence
不一定足够强大,因为lfence
仅耗尽ROB,与指令执行有关而不是获取,而mfence
处理数据加载/存储。如果您无法使用 cpuid
,那么对于这种情况,serialize
是一个不错的选择。
(即使是写入器中对齐的 8 字节块内的原子 RMW 或原子存储也是不够的。在某些微架构上,我认为从 L1i 缓存中未对齐的 16 字节块代码获取是可能的,因此读者可能会撕裂在任何边界。)
嗯,我想这很有帮助:lfence。参考《64-ia-32-architectures-software-developer-manual》Vol.2B 4-301