zoukankan      html  css  js  c++  java
  • Linux Kernel CMPXCHG函数分析

    原文地址:http://blog.csdn.net/penngrove/article/details/44175387

    最近看到Linux Kernel cmpxchg的代码,对实现很不理解。上网查了内嵌汇编以及Intel开发文档,才慢慢理解了,记录下来以享和我一样困惑的开发者。其实cmpxchg实现的原子操作原理早已被熟知:

    cmpxchg(void* ptr, int old, int new),如果ptr和old的值一样,则把new写到ptr内存,否则返回ptr的值,整个操作是原子的。在Intel平台下,会用lock cmpxchg来实现,这里的lock个人理解是锁住内存总线,这样如果有另一个线程想访问ptr的内存,就会被block住。

    好了,让我们来看Linux Kernel中的cmpxchg(网上找来的,我自己机器上没找到对应的头文件,据说在include/asm-i386/cmpxchg.h)实现:

    01./* TODO: You should use modern GCC atomic instruction builtins instead of this. */  
    02.#include <stdint.h>  
    03.#define cmpxchg( ptr, _old, _new ) {   
    04.  volatile uint32_t *__ptr = (volatile uint32_t *)(ptr);     
    05.  uint32_t __ret;                                       
    06.  asm volatile( "lock; cmpxchgl %2,%1"             
    07.    : "=a" (__ret), "+m" (*__ptr)                  
    08.    : "r" (_new), "0" (_old)                       
    09.    : "memory");                   
    10.  );                                               
    11.  __ret;                                           
    12.}  
    
    /* TODO: You should use modern GCC atomic instruction builtins instead of this. */
    #include <stdint.h>
    #define cmpxchg( ptr, _old, _new ) { 
      volatile uint32_t *__ptr = (volatile uint32_t *)(ptr);   
      uint32_t __ret;                                     
      asm volatile( "lock; cmpxchgl %2,%1"           
        : "=a" (__ret), "+m" (*__ptr)                
        : "r" (_new), "0" (_old)                     
        : "memory");				 
      );                                             
      __ret;                                         
    }

    主要要看懂内嵌汇编,c的内嵌汇编格式是

    01.asm ( assembler template  
    02.    : output operands                   (optional)  
    03.    : input operands                    (optional)  
    04.    : clobbered registers list          (optional)  
    05.    );  
    
    asm ( assembler template
        : output operands                   (optional)
        : input operands                    (optional)
        : clobbered registers list          (optional)
        );

    output operands和inpupt operands指定参数,它们从左到右依次排列,用','分割,编号从0开始。以cmpxchg汇编为例,(__ret)对应0,(*__ptr)对应1,(_new)对应2,(_old)对应3,如果在汇编中用到"%2",那么就是指代_new,"%1"指代(*__ptr)。

    "=a"是说要把结果写到__ret中,而且要使用eax寄存器,所以最后写结果的时候是的操作是mov eax, ret (eax==>__ret)。"r" (_new)是要把_new的值读到一个通用寄存器中使用。

    在cmpxchg中,注意"0"(_old),这个是困惑我的地方,它像告诉你(_old)和第0号操作数使用相同的寄存器或者内存,即(_old)的存储在和0号操作数一样的地方。在cmpxchg中,就是说_old和__ret使用一样的寄存器,而__ret使用的寄存器是eax,所以_old也用eax。

    明白了这些,再来看cmpxchgl,在Intel开发文档上说:

    0F B1/r        CMPXCHG r/m32, r32           MR Valid Valid*          Compare EAX with r/m32. If equal, ZF is set
                                                                                                         and r32 is loaded into r/m32. Else, clear ZF
                                                                                                         and load r/m32 into EAX.

    翻译一下:

    比较eax和目的操作数(第一个操作数)的值,如果相同,ZF标志被设置,同时源操作数(第二个操作)的值被写到目的操作数,否则,清ZF标志,并且把目的操作数的值写回eax。

    好了,把上面这句话套在cmpxchg上就是:

    比较_old和(*__ptr)的值,如果相同,ZF标志被设置,同时_new的值被写到(*__ptr),否则,清ZF标志,并且把(*__ptr)的值写回_old。

    很明显,符合我们对cmpxchg的理解。

    另:Intel开发手册上说lock就是让CPU排他地使用内存。


     

  • 相关阅读:
    搭建kafka高级消费 (high-consumer)php7
    kafka搭建到配置borker集群(项目开发-区块链)
    快速提高谷歌浏览器(Chrome)自带下载器的网速
    利用IO和File类实现拷贝文件目录问题
    随机红包小算法
    二叉树前序中序后序层序遍历问题
    荷兰国旗问题
    二分法查找
    找出数组中最大值and索引
    数组元素反转
  • 原文地址:https://www.cnblogs.com/linuxbug/p/4840141.html
Copyright © 2011-2022 走看看