multithreading - 这个 CMPXCHG16B 指令的仿真有什么问题?

标签 multithreading assembly x86 thread-safety atomic

我试图在一个地方运行一个使用 CMPXCHG16B 指令的二进制程序,不幸的是我的 Athlon 64 X2 3800+ 不支持它。这很棒,因为我认为这是一个编程挑战。用洞穴跳跃来执行该指令似乎并不难,所以我就是这样做的,但是有些东西不起作用,程序只是卡住在一个循环中。也许有人可以告诉我我的 CMPXCHG16B 实现是否错误?

首先,我要模拟的实际机器代码是这样的:

f0 49 0f c7 08                lock cmpxchg16b OWORD PTR [r8]

摘自英特尔手册描述CMPXCHG16B:

Compare RDX:RAX with m128. If equal, set ZF and load RCX:RBX into m128. Else, clear ZF and load m128 into RDX:RAX.

首先,我用我的仿真程序跳转到代码洞穴来替换指令的所有 5 个字节,幸运的是,跳转恰好占用了 5 个字节!跳转其实是一个call指令e8,但也可以是一个jmpe9,都可以。

e8 96 fb ff ff            call 0xfffffb96(-649)

这是一个以二进制补码编码的 32 位带符号偏移量的相对跳转,该偏移量指向相对于下一条指令地址的代码洞穴。

接下来是我要跳转到的仿真代码:

PUSH R10
PUSH R11
MOV r10, QWORD PTR [r8]
MOV r11, QWORD PTR [r8+8]
TEST R10, RAX
JNE ELSE
TEST R11, RDX
JNE ELSE
MOV QWORD PTR [r8], RBX
MOV QWORD PTR [r8+8], RCX
JMP END
ELSE:
MOV RAX, r10
MOV RDX, r11
END:
POP R11
POP R10
RET

就我个人而言,我很满意,我认为它符合手册中给出的功能规范。它将堆栈和两个寄存器 r10r11 恢复到原来的顺序,然后恢复执行。唉,它不起作用!那是代码有效,但程序的行为就像在等待小费和烧电一样。这表明我的仿真并不完美,我无意中打破了它的循环。你觉得有什么问题吗?

我注意到这是它的一个原子变体——拥有 lock 前缀。我希望除了争辩说我做错之外还有别的什么。或者有没有办法模拟原子性?

最佳答案

无法模拟 lock cmpxchg16b。如果对目标地址的所有访问都与单独的锁同步,这是有可能的,但这包括所有其他指令,包括对对象任一半的非原子存储,以及原子读取-修改-写入(如 xchg , lock cmpxchg, lock add, lock xadd) 与 16 字节对象的一半(或其他部分)。

您可以像在此处所做的那样模拟 cmpxchg16b(没有 lock),并使用来自@Fifoernik 的答案的错误修复。这是一个有趣的学习练习,但在实践中并不是很有用,因为使用 cmpxchg16b 的实际代码总是使用 lock 前缀。

大多数情况下,非原子替换将起作用,因为来自另一个内核的缓存行无效很少会在两条附近指令之间的小时间窗口内到达。 这并不意味着它是安全的,它只是意味着当它偶尔失败时真的很难调试。如果您只是想让游戏为自己使用,并且可以接受偶尔的锁定/错误,这可能会很有用。对于正确性很重要的任何事情,您都不走运。


What about MFENCE? Seems to be what I need.

MFENCE 在加载和存储之前、之后或之间不会阻止另一个线程看到一半写入的值(“撕裂”),或者在您的代码生成之后修改数据比较成功的决定,但在存储之前。它可能会缩小漏洞的窗口,但它不能关闭它,因为 MFENCE 只会阻止重新排序我们自己的存储和加载的全局可见性。它不能阻止来自另一个核心的存储在加载之后但在存储之前对我们可见。这需要一个原子的读-修改-写总线周期,这就是 locked 指令的用途。

进行两次 8 字节原子比较交换将解决漏洞窗口问题,但仅针对每一半单独进行,留下“撕裂”问题。

Atomic 16B 加载/存储解决了撕裂问题,但没有解决加载和存储之间的原子性问题。它是 possible with SSE on some hardware , 但 x86 ISA the way 8B naturally-aligned loads and stores are 不保证是原子的.


Xen 的 lock cmpxchg16b 仿真:

Xen 虚拟机有一个 x86 模拟器,我猜是虚拟机在一台机器上启动并迁移到功能较弱的硬件的情况。它通过获取全局锁来模拟 lock cmpxchg16b,因为没有其他方法。如果有 一种“正确”模拟它的方法,我相信 Xen 会这样做。

this mailing list thread 中所述,当一个内核上的模拟版本访问与另一个内核上的非模拟指令相同的内存时,Xen 的解决方案仍然不起作用。 (原生版本不尊重全局锁)。

另见 this patch on the Xen mailing list这会更改 lock cmpxchg8b 仿真以支持 lock cmpxchg8block cmpxchg16b

根据emulate cmpxchg16b的搜索结果,我也发现KVM的x86模拟器也不支持cmpxchg16b

我认为所有这些都是很好的证据,证明我的分析是正确的,并且不可能安全地模仿它。

关于multithreading - 这个 CMPXCHG16B 指令的仿真有什么问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38219226/

相关文章:

c++ - 快速将 2 个 double 数组交织成具有 2 个 float 和 1 个 int(循环不变)成员的结构数组,并使用 SIMD double->float 转换?

x86 - 今天的 BIOS 怎么能大于 64KB?

c - 二锁并发队列算法实现问题

java - 多个用户同时访问的 Web 应用程序的 ExecutorService 线程池大小

assembly - 如何仅使用 mov、add、sub、neg 限制 4 条指令中的 x=2a+3b?

c++ - 具有可变长度写入的多生产者多消费者无锁非阻塞环形缓冲区

android - 在服务调用的线程中获取上下文

java - 如何插入代码使用尽可能多的 CPU 资源?

c - 警告 : Function must be extern error in C

c - OS X - 在 C 中使用内联汇编时架构 i386 的 undefined symbol