14.3.2 x86架构下的原子操作

Nginx要在源代码中实现对整型的原子操作,自然必须通过内联汇编语言直接操作硬件才能做到,本节以基于x86的SMP多核架构为例来看看Nginx是如何实现这两个基本的原子操作的(由于参考着x86架构下的实现即可以简单地推导出其他架构下的实现,故其他架构下的原子操作实现方法不再一一说明)。

使用GCC编译器在C语言中嵌入汇编语言的方式是使用asm关键字,如下所示。


asmvolatile(汇编语句部分

:输出部分

/可选/

:输入部分

/可选/

:破坏描述部分

/可选/

);


以上加入的volatile关键字用于限制GCC编译器对这段代码做优化。

这段内联的汇编语言包括4个部分。

(1)汇编语句部分

引号中所包含的汇编语句可以直接用占位符%来引用C语言中的变量(最多10个,%0~%9)。

下面简单介绍一下随后用到的两个汇编语句,先来看看cmpxchgl r,[m]这个语句,Nginx源代码中对这一汇编语句有一段伪代码注释,如下所示。


//如果eax寄存器中的值等于m

if(eax==[m]){

//将z f标志位设为1

zf=1;

//将m值设为r

[m]=r;

//如果eax寄存器中的值不等于m

}else{

//zf标志位设为0

zf=0;

//将eax寄存器中的值设为m

eax=[m];

}


从上面这段伪代码可以看出,cmpxchgl r,[m]语句首先会用m比较eax寄存器中的值,如果相等,则把m的值设为r,同时将zf标志位设为1;否则将zf标志位设为0。

再看一个语句sete[m],它正好配合着上面的cmpxchgl语句使用,这里不妨简单地认为它的作用就是将zf标志位中的0或者1设置到m中。

(2)输出部分

这部分可以将寄存器中的值设置到C语言的变量中。

(3)输入部分

可以将C语言中的变量设置到寄存器中。

(4)破坏描述部分

通知编译器使用了哪些寄存器、内存。

简单了解了GCC如何内联汇编语言后,下面来看看ngx_atomic_cmp_set方法的实现,如下所示。


static ngx_inline ngx_atomic_uint_t

ngx_atomic_cmp_set(ngx_atomic_t*lock,ngx_atomic_uint_t old,

ngx_atomic_uint_t set)

{

u_char res;

//在C语言中嵌入汇编语言

asmvolatile(

//多核架构下首先锁住总线

"lock;"

//将lock的值与eax寄存器中的old相比较,如果相等,则置lock的值为set

"cmpxchgl%3,%1;"

//cmpxchgl的比较若是相等,则把zf标志位1写入res变量,否则res为0

"sete%0;"

:"=a"(res):"m"(*lock),"a"(old),"r"(set):"cc","memory");

return res;

}


现在简单地说明一下上述代码,在嵌入汇编语言的输入部分,"m"(lock)表示lock变量是在内存中,操作lock时直接通过内存(不使用寄存器)处理,而"a"(old)表示把old变量写入eax寄存器中,"r"(set)表示把set变量写入通用寄存器中,这些都是在为cmpxchgl语句做准备。"cmpxchgl%3,%1"相当于"cmpxchgl setlock"(含义参照上面介绍过的伪代码)。这3行汇编语句的意思如下:首先锁住总线防止多核的并发执行,接着判断原子变量lock与old值是否相等,若相等,则把lock值设为set,同时设res为1,方法返回;若不相等,则设res为0,方法返回。

在了解ngx_atomic_fetch_add方法前,再介绍一个汇编语句xaddl。下面先来看看Nginx对"xaddl r,[m]"语句做的伪码注释,如下所示。


temp=[m];

[m]+=r;

r=temp;


可以看到,xaddl执行后[m]值将为r和[m]之和,而r中的值为原[m]值。现在看看ngx_atomic_fetch_add方法是如何实现的,如下所示。


static ngx_inline ngx_atomic_int_t

ngx_atomic_fetch_add(ngx_atomic_t*value,ngx_atomic_int_t add)

{

asmvolatile(

//首先锁住总线

"lock;"

//value的值将会等于原先value值与add值之和,而add为原*value值

"xaddl%0,%1;"

:"+r"(add):"m"(*value):"cc","memory");

return add;

}


可见,ngx_atomic_fetch_add将使得value原子变量的值加上add,同时返回原先value的值。