Intel x86比较交换指令cmpxchg的作用与原理

cmpxchg是一个比较交换指令,原意是Compare and Exchange。

本文根据《Intel64和IA-32架构软件开发者手册》第2卷(《Intel® 64 and IA-32 Architectures Software Developer’s Manual》 Volume 2 (2A, 2B, 2C & 2D): Instruction Set Reference, A-Z),总结一下cmpxchg指令的作用,以及其实现原理。

指令格式

cmpxchg dest,src

将AL、AX、EAX或RAX寄存器中的值与第一个操作数dest(目标操作数)进行比较。
如果两个值相等,则将第二个操作数src(源操作数)加载到目标操作数中。
如果不相等,则目标操作数被加载到AL、AX、EAX或RAX寄存器中。
RAX寄存器仅在64位模式下可用。

该指令可以与LOCK锁前缀一起使用,使得指令以原子的方式执行。
为了简化到处理器总线的接口,不管比较结果是否相等,目标操作数都将接收一个写周期。
如果比较失败(不相等),则目标操作数将会被回写(为原来的值);否则,源操作数将被写入目标操作数。
(处理器不会产生锁读,也不会产生锁写。)

在64位模式下,该指令的默认操作数大小为32位。
如果使用REX.R前缀,允许访问附加的寄存器(R8-R15)。
如果使用REX.W前缀,可以将操作数大小提升为64位。

以64位模式为例

CMPXCHG r/m32, r32

指令说明:
比较寄存器EAX和目标操作数r/m32的值是否相等。(这里提到的值,是指寄存器或内存单元中的值)
如果相等,则设置ZF标志位(置为1),并将寄存器r32的值保存到操作数r/m32中,替换掉旧值;
如果不相等,则清除ZF标志位(置为0),并将寄存器r/m32的值加载到寄存器EAX中,更新EAX为目标操作数的值。

其中:
r32:表示源操作数,用于暂存新值。
r/m32:表示目标操作数。如果指令执行成功,其对应地址存储的值将会被替换为新值。
EAX:一个通用寄存器,用于暂存旧值,用来与目标操作数进行比较。

操作数符号的详细含义

r32:
表示一个双字(32位)的通用寄存器:EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI;
或者如果是在64位模式下使用REX.R,则表示一个可用的双字寄存器(R8D-R15D)。

r/m32:
表示一个双字(32位)通用寄存器或者内存操作数,用于操作数大小为32位的指令。(如使用32位的寄存器、32位的内存单元)
双字通用寄存器有:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI。
在64位模式下使用REX.R时,可以使用附加的双字寄存器(R8D-R15D)。

IA-32架构兼容性

在Intel486处理器之前的Intel处理器上不支持该指令。

指令伪代码

TEMP := DEST // 目标操作数的值保存到TEMP
IF accumulator = TEMP // 比较旧值与目标值是否相等
  THEN // 如果相等,则设置ZF为1,并将新值保存到目标操作数中
    ZF := 1; // 设置ZF为1
    DEST := SRC; // 将新值保存到目标操作数中
  ELSE // 如果不相等
    ZF := 0; // 清除ZF,设置ZF为0
    accumulator := TEMP; // 将目标操作数的值保存到累加器
    DEST := TEMP; // 将TEMP的值回写到目标操作数
FI;

其中,accumulator表示累加器,指AL、AX、EAX或者RAX,具体取决于执行的是字节、单字、双字还是四字比较。
TEMP用于暂存目标操作数,在比较失败时赋值给累加器accumulator,并回写到目标操作数DEST。
ZF是状态寄存器中的一个零标志(Zero Flag)位,如果运算结果为零(0),则设置(1或true),否则进行重置。

参考

Intel文档:https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html

cmpxchg指令:https://www.felixcloutier.com/x86/cmpxchg

x86寄存器:http://www.cs.virginia.edu/~evans/cs216/guides/x86.html

ZF标志:https://en.wikipedia.org/wiki/Zero_flag


---转载本站文章请注明作者和出处 二进制之路(binarylife.icu),请勿用于任何商业用途---

留下评论