x86 CMPXCHG 是原子的吗?如果是,为什么它需要 LOCK?
Is x86 CMPXCHG atomic, if so why does it need LOCK?
This instruction can be used with a LOCK
prefix to allow the instruction to be executed atomically.
我的问题是
可以CMPXCHG
用内存地址操作吗?从文档中似乎不能,但任何人都可以确认只适用于寄存器中的实际值,而不是内存地址吗?
如果CMPXCHG
不是原子的并且必须通过LOCK CMPXCHG
(带有LOCK
前缀)来实现高级语言级别的CAS,那么目的是什么引入这样的指令吗?
(我是从高级语言的角度来问的。也就是说,如果无锁算法必须在x86平台上翻译成LOCK CMPXCHG,那么它仍然以LOCK为前缀。这意味着无锁算法并不比精心编写的同步锁/互斥锁(至少在 x86 上)更好。这似乎也使裸 CMPXCHG 指令变得毫无意义,因为我想引入它的主要目的是支持这种无锁操作.)
LOCK前缀是为了锁定当前命令的内存访问,使得CPU管道中的其他命令不能同时访问内存。使用 LOCK 前缀,命令的执行不会由于同时执行的其他命令的内存访问而被 CPU 管道中的另一个命令中断。
INTEL手册说:
The LOCK prefix can be prepended only to the following in structions
and only to those forms of the instructions where the destination
operand is a memory operand: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG,
CMPXCH8B, CMPXCHG16B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and
XCHG. If the LOCK prefix is used with one of these instructions and
the source operand is a memory operand, an undefined opcode exception
(#UD) may be generated.
您将高级锁与碰巧命名为 LOCK
的低级 CPU 功能混淆了。
无锁算法试图避免的高级锁可以保护执行可能需要任意时间的任意代码片段,因此,这些锁将不得不让线程进入等待状态,直到锁可用,这是一个昂贵的操作,例如意味着维护一个等待线程队列。
这与 CPU LOCK
前缀功能完全不同,后者仅保护单个指令,因此可能仅在该单个指令的持续时间内保持其他线程。由于这是由 CPU 本身实现的,因此不需要额外的软件工作。
因此,开发无锁算法的挑战不在于完全消除同步,它归结为将代码的关键部分减少为单个原子操作,该操作将由 CPU 本身提供.
您真正要问的部分似乎是:
Why isn't the lock
prefix implicit for cmpxchg
with a memory operand, like it is for xchg
(since 386)?
简单的答案(其他人给出的)就是英特尔以这种方式设计的。但这引出了一个问题:
Why did Intel do that? Is there a use-case for cmpxchg
without lock
?
在单CPU系统上,cmpxchg
是相对于其他线程或任何其他代码的原子运行ning 在同一个 CPU 核心 上。 (但对于像内存映射 I/O 设备或对正常内存进行 DMA 读取的设备这样的“系统”观察者来说不是这样,因此 lock cmpxchg
即使在单处理器 CPU 设计上也是相关的)。 =37=]
上下文切换只能发生在中断上,中断发生在指令之前或之后,而不是在中间。在同一个 CPU 上的任何代码 运行ning 都会将 cmpxchg
视为完全执行或根本不执行 .
例如,Linux 内核通常编译时支持 SMP,因此它使用 lock cmpxchg
用于原子 CAS。但是当在单处理器系统上启动时,它会将 lock
前缀修补到 nop
任何代码被内联的地方,因为 nop
cmpxchg
运行s比 lock cmpxchg
快得多。有关详细信息,请参阅此 LWN article about Linux's "SMP alternatives" system。它甚至可以在热插拔第二个 CPU.
之前修补回 lock
前缀
阅读有关单处理器系统上单指令原子性的更多信息, and in on Can num++
be atomic for int num
. See ,了解有关原子性如何真正工作/实现读-修改-写指令的更多详细信息,例如 lock cmpxchg
。
(同样的推理也适用于 cmpxchg8b
/ cmpxchg16b
和 xadd
,它们通常仅用于同步/原子操作,而不是制作单线程代码 运行 更快。当然,像 add [mem], reg
这样的内存目标指令 在 lock add [mem], reg
情况之外很有用。)
This instruction can be used with a
LOCK
prefix to allow the instruction to be executed atomically.
我的问题是
可以
CMPXCHG
用内存地址操作吗?从文档中似乎不能,但任何人都可以确认只适用于寄存器中的实际值,而不是内存地址吗?如果
CMPXCHG
不是原子的并且必须通过LOCK CMPXCHG
(带有LOCK
前缀)来实现高级语言级别的CAS,那么目的是什么引入这样的指令吗?
(我是从高级语言的角度来问的。也就是说,如果无锁算法必须在x86平台上翻译成LOCK CMPXCHG,那么它仍然以LOCK为前缀。这意味着无锁算法并不比精心编写的同步锁/互斥锁(至少在 x86 上)更好。这似乎也使裸 CMPXCHG 指令变得毫无意义,因为我想引入它的主要目的是支持这种无锁操作.)
LOCK前缀是为了锁定当前命令的内存访问,使得CPU管道中的其他命令不能同时访问内存。使用 LOCK 前缀,命令的执行不会由于同时执行的其他命令的内存访问而被 CPU 管道中的另一个命令中断。 INTEL手册说:
The LOCK prefix can be prepended only to the following in structions and only to those forms of the instructions where the destination operand is a memory operand: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, CMPXCHG16B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG. If the LOCK prefix is used with one of these instructions and the source operand is a memory operand, an undefined opcode exception (#UD) may be generated.
您将高级锁与碰巧命名为 LOCK
的低级 CPU 功能混淆了。
无锁算法试图避免的高级锁可以保护执行可能需要任意时间的任意代码片段,因此,这些锁将不得不让线程进入等待状态,直到锁可用,这是一个昂贵的操作,例如意味着维护一个等待线程队列。
这与 CPU LOCK
前缀功能完全不同,后者仅保护单个指令,因此可能仅在该单个指令的持续时间内保持其他线程。由于这是由 CPU 本身实现的,因此不需要额外的软件工作。
因此,开发无锁算法的挑战不在于完全消除同步,它归结为将代码的关键部分减少为单个原子操作,该操作将由 CPU 本身提供.
您真正要问的部分似乎是:
Why isn't the
lock
prefix implicit forcmpxchg
with a memory operand, like it is forxchg
(since 386)?
简单的答案(其他人给出的)就是英特尔以这种方式设计的。但这引出了一个问题:
Why did Intel do that? Is there a use-case for
cmpxchg
withoutlock
?
在单CPU系统上,cmpxchg
是相对于其他线程或任何其他代码的原子运行ning 在同一个 CPU 核心 上。 (但对于像内存映射 I/O 设备或对正常内存进行 DMA 读取的设备这样的“系统”观察者来说不是这样,因此 lock cmpxchg
即使在单处理器 CPU 设计上也是相关的)。 =37=]
上下文切换只能发生在中断上,中断发生在指令之前或之后,而不是在中间。在同一个 CPU 上的任何代码 运行ning 都会将 cmpxchg
视为完全执行或根本不执行 .
例如,Linux 内核通常编译时支持 SMP,因此它使用 lock cmpxchg
用于原子 CAS。但是当在单处理器系统上启动时,它会将 lock
前缀修补到 nop
任何代码被内联的地方,因为 nop
cmpxchg
运行s比 lock cmpxchg
快得多。有关详细信息,请参阅此 LWN article about Linux's "SMP alternatives" system。它甚至可以在热插拔第二个 CPU.
lock
前缀
阅读有关单处理器系统上单指令原子性的更多信息num++
be atomic for int num
. See lock cmpxchg
。
(同样的推理也适用于 cmpxchg8b
/ cmpxchg16b
和 xadd
,它们通常仅用于同步/原子操作,而不是制作单线程代码 运行 更快。当然,像 add [mem], reg
这样的内存目标指令 在 lock add [mem], reg
情况之外很有用。)