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

4,192 阅读3分钟

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文档:software.intel.com/content/www…

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

x86寄存器:www.cs.virginia.edu/~evans/cs21…

ZF标志:en.wikipedia.org/wiki/Zero_f…

关于我

公众号:二进制之路

教程:996geek.com

博客:binarylife.icu