Linux Kernel CMPXCHG函數分析

最近看到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排他地使用內存。

 

原文地址:

http://blog.csdn.net/penngrove/article/details/44175387

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章