Android原子操作的實現原理

Android原子操作的實現方式和CPU的架構有密切關係,現在的原子操作一般都是在CPU指令級別實現的,這樣不但簡單,而且效率非常高。下面看看arm平臺下Android是如何實現原子操作的。

雖然原子操作的接口函數有十來個,但是實際上只有兩個函數中通過彙編代碼實現了原子操作:函數android_atomic_addandroid_atomic_cas,其他的函數都是在內部調用它們而已。這兩個函數的原理差不多,下面我們以加法爲例來了解一下原子變量的實現原理。函數android_atomic_add的代碼如下:

extern ANDROID_ATOMIC_INLINE

int32_t android_atomic_add(int32_t increment, volatile int32_t *ptr)

{

    int32_t prev, tmp, status;

    android_memory_barrier();

    do {

        __asm__ __volatile__ ("ldrex %0, [%4]\n"

                              "add %1, %0, %5\n"

                              "strex %2, %1, [%4]"

                              : "=&r" (prev), "=&r" (tmp),

                                "=&r" (status), "+m" (*ptr)

                              : "r" (ptr), "Ir" (increment)

                              : "cc");

    } while (__builtin_expect(status != 0, 0));

    return prev;

}  

上面代碼中的宏ANDROID_ATOMIC_INLINE的定義是:

#define ANDROID_ATOMIC_INLINE inline __attribute__((always_inline))

實際上就是把函數規定爲inline函數。

android_memory_barrier是告訴CPU這裏需要內存屏障。下節會介紹內存屏障。

接下來是一段內嵌彙編,這段彙編可以用僞代碼來表示:

do {

ldrex  prev[ptr]

add  tmp,  prev,  increment

strex  status,  tmp, [ptr]

} whiile(status != 0)

add指令的前後有兩條看上去比較陌生的指令:ldrexstrex。這兩條是AMRV6新引入的同步指令。ldrex指令的作用是把指針ptr指向的內容放到prev變量中,同時給執行處理器做一個標記(tag),標記上指針ptr的地址,表示這個內存地址已經有一個CPU正在訪問。當執行到strex指令時,它會檢查是否存在ptr的地址標記,如果標記存在,strex指令會把add指令執行的的結果寫入指針ptr指向的地址,並且返回0,然後清除該標記。返回的結果0會放在status變量中,這樣循環將結束。

如果在strex指令執行前發生了線程的上下文切換,在切換回來後,ldrx指令設置的標誌將會被清除。這時再執行strex指令時,由於沒有了這個標誌,strex指令將不會完成對ptr指針的存儲操作,而且status變量中的返回結果是1。這樣循環不能結束,重新開始執行,直到成功爲止。

__builtin_expectgcc的內建函數,有兩個參數,第一個參數是一個表達式,第二個參數是一個值。表達式的計算結果也是函數的結果。__builtin_expect是用來告訴gcc預測表達式更可能的值是什麼,這樣gcc會根據預測值來優化代碼。代碼中表達的含義是預測“status!=0”這個表達式的值爲“0”,也就是預測while循環將結束。

 

內嵌彙編可以參考本人的博文:gcc內嵌彙編介紹


 

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