2011-06-24 80 views
5

在我們的產品中,我們有一個內嵌的互斥鎖實現,使用各種平臺和編譯器特定的方法來實現特定於硬件的部分。對於一些試圖「欺騙」的過度優化的代碼,我們的一個「規則」是,如果一個變量在互斥體之外訪問,那麼該變量必須聲明爲volatile。我認爲這也適用於不透明的互斥體實現(如pthread_mutex_lock/unlock),這引發了一場有趣的爭論。使用gcc內聯彙編的易失性與編譯器屏障

有人曾聲稱這是編譯器錯誤的表示(尤其是當互斥體實現內聯並且對編譯器「不透明」時)。我給下面的例子來爭這個

int v = pSharedMem->myVariable ; 

__asm__ __volatile__(("isync" : : :"memory")) 

v = pSharedMem->myVariable ; 

在這種LinuxPPC GCC的代碼片段,編譯器不具備的iSync中的運行時間影響的任何知識,比我們可以通過內存約束告訴它其他。您會在互斥量採集的最後找到這樣的異步指令,以防止互斥量在實際持有之前成功獲取互斥量之後的任何指令執行(因此,如果在異步之前執行了加載,它會必須被丟棄)。

在此代碼片段,我們有編譯器屏障防止所述代碼的重寫,就好像它是在以下

int v = pSharedMem->myVariable ; 
v = pSharedMem->myVariable ; 

__asm__ __volatile__(("isync" : : :"memory")) 

__asm__ __volatile__(("isync" : : :"memory")) 

int v = pSharedMem->myVariable ; 
v = pSharedMem->myVariable ; 

(即:這兩個編譯器重新排序應該被禁止的易失性屬性)

我們也有isync本身,它可以防止第一次重新排序運行時間(但我不認爲阻止第二個不那麼有趣)。

但是,我的問題是,如果myVariable是而不是聲明爲volatile,那麼「memory」約束是否足以讓gcc在isync之後必須重新加載「v」?我仍然傾向於對這種模式進行volatile,因爲這種代碼對所有平臺特定的編譯器內置函數都太敏感。也就是說,如果我們將討論簡化爲GCC和此代碼片段,那麼這個asm內存約束是否足以讓代碼由一對加載而不是一個加載生成?

回答

2

__asm__ __volatile__"memory" clobber被要求並將充當重新排序的障礙。 volatile上的變量是不必要的。事實上,如果您查看Linux內核定義atomic_t,它不會使用任何volatile修飾符,並完全依賴具有適當約束的__asm__ __volatile__語句。另一方面,我認爲volatile本身並不實際上禁止重新排序,而只是完全緩存和優化這個值,所以它對於同步目的是毫無價值的。

+0

是的,但我並不是談論原子或鎖定詞的易失性,而是關於互斥體保護的數據。我也並不是暗示volatile具有任何同步值(即:同步是由互斥鎖提供的,我想知道受互斥鎖保護的數據)。如果數據也在互斥體之外訪問,則在獲取互斥體後強制重新加載該值需要volatile。 –

+0

這裏有一些額外的上下文(產生這個問題的原始討論) http://peeterjoot.wordpress.com/2011/06/21/an-unusual-volatile-required-example/ –

+0

@Peeter:我幾乎確定樣本存在錯誤。任何非內聯函數調用總是強制從存儲器中重新獲取非本地數據,因爲編譯器無法確定該函數沒有簡單的指向該數據的指針並且只是將其寫入,而這可能發生在單線程情況下那裏定義了語義。另一方面,代碼在有或者沒有volatile的情況下是不正確的,因爲在一些平臺上的讀取不是原子的。訪問共享變量時不需要鎖定,您需要使用特殊的同步原子操作。 –