cmpxchgl

2012年11月17日 发表评论 阅读评论 10,200 次浏览

白天在公司看DPDK的代码,一个使用cmpxchgl指令实现的支持多生产者-多消费者的内存池,挺有意思的,里面有一处关键代码为rte_atomic32_cmp_set()(好像是这个名字?),其实现在Nginx代码内也有,现在家里电脑上没有DPDK的代码,所以就直接来看看Nginx内的具体实现:

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;

    __asm__ volatile (

         NGX_SMP_LOCK
    "    cmpxchgl  %3, %1;   "
    "    sete      %0;       "

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

    return res;
}

这段代码来之ngx_gcc_atomic_x86.h,虽然其它文件,比如ngx_gcc_atomic_amd64.h也有,但基本一致,所以暂就以它为例。

要看懂这段代码,首先需对Linux下C中嵌入汇编有所了解,这可以关键字“GCC-Inline-Assembly-HOWTO”进行Google ,比如:http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html,简单介绍也就是嵌入汇编的基本格式为:

asm ( assembler template 
    : output operands                  /* optional */
    : input operands                   /* optional */
    : list of clobbered registers      /* optional */
    );

因此对照来看,函数ngx_atomic_cmp_set()内:
汇编模板为两条或三条,因为宏NGX_SMP_LOCK展开可能为空(单核)或为”lock;”(多核);
输出操作数为res(对应的寄存器为eax,这里x86限定为32位系统,下同);
输入操作数为*lock(memory内存)、old(eax寄存器)、set(任意可用通用寄存器)
附带影响:cc表示标志寄存器会被附带修改、memory表示内存会被附带修改。

在汇编模板里,各个输入\输出操作数是以%0、%1、%2,…等进行指代的,因此汇编模板可展开为(用大括号括起来,以示区分):

lock;
cmpxchgl  {set}, {*lock};
sete      {res};

第一条指令lock,表示锁定总线,这显然只有在多核情况下才有必要。
http://faydoc.tripod.com/cpu/lock.htm

第二条真实指令为cmpxchg,末尾的l表示操作数长度为4,这条指令有点复杂,它用到了一个隐含的操作数,即eax。在前面的输入操作数中,我们看到了old,其对应的%2却并没有在汇编模板里出现,但通过修饰符a把它存放到了eax寄存器,因此这里被cmpxchg指令隐含使用。
指令cmpxchg比较eax(也就是{old})与{*lock}的值,如果相等,那么将{set}的值赋值给{*lock},同时标志寄存器ZF位置1;否则,将{*lock}的值赋值给eax,并且将标志寄存器ZF位置0。
相关介绍在这:http://faydoc.tripod.com/cpu/cmpxchg.htm,但注意这里是以Intel汇编语法进行描述的,而Intel汇编语法与AT&T汇编的操作数位置是一个反的。

第三条指令sete,如果标志寄存器ZF位为1,那么设置{res}为1。
http://faydoc.tripod.com/cpu/sete.htm
http://web.itu.edu.tr/kesgin/mul06/intel/intel_flags.html

因此,整体下来,函数:
ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
ngx_atomic_uint_t set);
的含义就是:如果*lock值等于old,那么将set值赋值给*lock,并返回1。一般情况,lock表示一把锁,而old为其未被任何进程持有的情况,即为0,而set值为1,也就是这样的调用:ngx_atomic_cmp_set(lock, 0, 1)返回1则表示获取锁成功。

在DPDK的内存池里就是利用同样的东东来实现多用户(多生产者或多消费者)情况下修改环形缓冲区的队头或队尾指针:do {…} while ((success = rte_atomic32_cmp_set(r->head, old_head, new_head) == 0))。

转载请保留地址:http://www.lenky.info/archives/2012/11/2028http://lenky.info/?p=2028


备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。

法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以Email或书面等方式告知,本站将及时删除相关内容或链接。

  1. memon
    2013年2月17日14:05 | #1

    写的很详细。 有新帖子weibo也发下吧。 发现好多帖子错过了

  1. 本文目前尚无任何 trackbacks 和 pingbacks.