一、背景

又是关于汇编,头真是大,当年欠吴旭老师的汇编终究还是要还的。
在看nws源码的时候,里面有这样一段代码

static __inline__ unsigned long   
atomic_cmp_set(volatile void *ptr, unsigned long old,
unsigned long set)
{
unsigned long prev;

__asm__ volatile(LOCK"cmpxchgq %2,%1"
: "=a"(prev), "+m"(*(volatile long *)(ptr))
: "r"(set), "0"(old)
: "memory");

return prev;
}

二、基础知识

这段代码是内嵌汇编代码(C与C++代码混合在一起叫内嵌汇编),为什么要使用内嵌汇编代码?可以大大的提升代码的运行速度,在nginx中广泛应用了CAS(compare-and-swap)操作来完成进程同步,也就是nginx的无锁机制,linux内核大量代码亦是如此(比如include/asm-i386/cmpxchg.h)

2.1 内嵌汇编常识介绍

​​ GCC在C语言中内嵌汇编 asm volatile​​

内嵌汇编模板
asm ( assembler template

: output operands (optional)

: input operands (optional)

: list of clobbered registers
(optional)
);

2.2 cmpxchgq

cmpxchg是汇编指令
作用:比较并交换操作数.
如:CMPXCHG r/m,r 将累加器AL/AX/EAX/RAX中的值与首操作数(目的操作数)比较,如果相等,第2操作数(源操作数)的值装载到首操作数,zf置1。如果不等,首操作数的值装载到AL/AX/EAX/RAX并将zf清0

2.3 代码分析

以上面cmpxchg汇编为例, LOCK”cmpxchgq %2,%1” 就是汇编指令, output operands和inpupt operands指定参数,它们从左到右依次排列,用’,’分割,编号从0开始。,(prev)对应0,(* (volatile long )(ptr))对应1,(set)对应2,(old)对应3,如果在汇编中用到”%2”,那么就是指代set,”%1”指代 ( (volatile long *)(ptr))。
“=a”(prev) 和 “0”(old) 用的寄存器都是eax

一般cmpxchg有两种存在形式
cmpxchg bx, cx:
如果AX与BX相等,则CX送BX且ZF置1;否则BX送AX,且ZF清0
换成AT&T的格式就是
由于ATT汇编与Intel不同,操作数2,和操作数1互换。
cmpxchg %cx, %bx

针对代码解释
比较old和(* (volatile long * )(ptr))的值,如果相同,ZF标志被设置,同时set的值被写到(* (volatile long * )(ptr)),否则,清ZF标志,并且把(* (volatile long *)(ptr))的值写回 old。

在理解不了看看这个
/*
* “cmpxchgl r, [m]”:
*
* if (eax == [m]) {
* zf = 1;
* [m] = r;
* } else {
* zf = 0;
* eax = [m];
* }
*/

这篇文章也可以帮助理解:
​cpu cmpxchg 指令理解 (CAS)​​