關於原子操作
所謂原子操作,就是該操作絕不會在執行完畢前被任何其他任務或事件打斷,也就說,它的最小的執行單位,不可能有比它更小的執行單位,因此這裡的原子實際是使用了物理學裡的物質微粒的概念。
原子操作需要硬體的支援,因此是架構相關的,其API和原子類型的定義都定義在核心源碼樹的include/asm/atomic.h檔案中,它們都使用組合語言實現,因為C語言並不能實現這樣的操作。
原子操作主要用於實現資源計數,很多引用計數(refcnt)就是通過原子操作實現的。原子類型定義如下:
typedef struct { volatile int counter; } atomic_t;
volatile修飾欄位告訴gcc不要對該類型的資料做最佳化處理,對它的訪問都是對記憶體的訪問,而不是對寄存器的訪問。
原子操作API包括:
atomic_read(atomic_t * v); //該函數對原子類型的變數進行原子讀操作,它返回原子類型的變數v的值。 atomic_set(atomic_t * v, int i); //該函數設定原子類型的變數v的值為i。 void atomic_add(int i, atomic_t *v); //該函數給原子類型的變數v增加值i。 atomic_sub(int i, atomic_t *v); //該函數從原子類型的變數v中減去i。 int atomic_sub_and_test(int i, atomic_t *v); //該函數從原子類型的變數v中減去i,並判斷結果是否為0,如果為0,返回真,否則返回假。 void atomic_inc(atomic_t *v); //該函數對原子類型變數v原子地增加1。 void atomic_dec(atomic_t *v); //該函數對原子類型的變數v原子地減1。 int atomic_dec_and_test(atomic_t *v); //該函數對原子類型的變數v原子地減1,並判斷結果是否為0,如果為0,返回真,否則返回假。 int atomic_inc_and_test(atomic_t *v); //該函數對原子類型的變數v原子地增加1,並判斷結果是否為0,如果為0,返回真,否則返回假。 int atomic_add_negative(int i, atomic_t *v); //該函數對原子類型的變數v原子地增加I,並判斷結果是否為負數,如果是,返回真,否則返回假。 int atomic_add_return(int i, atomic_t *v); //該函數對原子類型的變數v原子地增加i,並且返回指向v的指標。 int atomic_sub_return(int i, atomic_t *v); //該函數從原子類型的變數v中減去i,並且返回指向v的指標。 int atomic_inc_return(atomic_t * v); //該函數對原子類型的變數v原子地增加1並且返回指向v的指標。 int atomic_dec_return(atomic_t * v); //該函數對原子類型的變數v原子地減1並且返回指向v的指標。
原子操作通常用於實現資源的引用計數,在TCP/IP協議棧的IP片段處理中,就使用了引用計數,片段隊列結構struct ipq描述了一個IP片段,欄位refcnt就是引用計數器,它的類型為atomic_t,當建立IP片段時(在函數ip_frag_create中),使用atomic_set函數把它設定為1,當引用該IP片段時,就使用函數atomic_inc把引用計數加1。
關於LOCK
LOCK首碼作用於單個指令上,它對中斷沒有任何影響,因為中斷只能在指令之間產生。LOCK首碼的真正作用是保持對系統匯流排的控制,直到整條指令執行完畢。它在一條指令多次訪問記憶體的時候相當有用。
比如一個共用計數器,我們需要對它進行原子遞增操作,需要做如下工作:
1)從記憶體讀取該計數器的值,臨時將其儲存在CPU內部寄存器中。 2)增加讀取到的值。 3)將被修改後的值寫回記憶體。
在x86體繫結構中,這個遞增操作可以在單個指令中完成,因此中斷不會對該遞增操作產生影響。但是該指令有兩次記憶體訪問操作,讀和寫,另外一個CPU或者核(core)可能同時對該計數器進行遞增操作。如果另外一個CPU/核(core)在第1步完成後,第3步完成前讀取該計數器的值,那麼兩個CPU/核(core)都使用被修改之前的計數器值並對其進行遞增操作。這樣就出現了錯誤的情況。
如果在此時使用了LOCK首碼,第一個核在對該計數器進行操作的時候,保持對匯流排的控制權,直到兩次訪問記憶體的操作都完畢。這樣做的結果是,第二個CPU/核(Core)會延遲一段時間,知道第一個CPU完成所有操作為止。
這裡的第二個CPU/核(core)可以是DMA操作,因此在使用DMA操作的時候尤其要小心。如果處理器使用了緩衝,事情可能由於緩衝一致性問題變得更糟糕。同樣,如果使用了虛擬記憶體,也會增加該問題的嚴重程度。因此,在單個處理器的特殊指令前,最好使用lock首碼。