若干彙編語言指令具有“讀--修改--寫”類型 。也就是說,他們訪問存儲單元兩次,第一次讀原值,第二次寫新值。
假定運行在兩個CPU上的兩個內核控制路徑試圖通過執行非原子操作來同時“讀--修改--寫”同一個存儲器單元。首先,兩個CPU都試圖讀同一個單元,但是存儲器仲裁器(對訪問RAM芯片的操作進行串行化的硬件電路)插手,只允許其中一個訪問而不讓另一個延遲。然而,當第一個讀操作已經完成後,延遲的CPU從哪個存儲器單元正好讀到同一個(舊)值。然而,兩個CPU都試圖向那個存儲器單元寫一新值,總線存儲器訪問再一次被存儲器總裁器串行化,最終,兩個寫操作都成功。但是,全局的結果是不對的,因爲兩個CPU寫入同一(新)值。因此,兩個交錯的“讀--修改--寫”操作了一個單獨的操作。
避免由於“讀--修改--寫”指令引起的競爭條件的最容易的辦法,就是確保這樣的操作在芯片級是原子的。任何一個這樣的操作都必須以單個指令執行,中斷不能中斷,且避免其他的CPU訪問同一存儲器單元。這些很小的原子操作可以建立在其他更靈活進制的基礎之上以創建臨界區。
在你編寫C代碼程序時,並不能保證編譯器會爲a = a +1 或甚至像 a ++這樣的操作使用一個原子指令。因此Linux內核提供了一個專門的 atomic_t 類型(一個原子訪問計數器)和一些專門的函數和宏, 這些函數和宏作用於atomic_t 類型的變量,並當做單獨的、原子的彙編語言指令來使用。在多處理器系統中,每條這樣的指令都有一個lock字節的前綴。
1. Linux 中原子操作
atomic_read(v) 返回*V
atomic_set(v, i) 把*v 置成 i
atomic_add(i,v) 給*v 增加 i
atomic_add_return(i,v) 把i 加到*v,返回*V的新值
atomic_sub(i, v) 從*v 中減去i
atomic_sub_reurn(i,v) 從*v減i, 返回*v的新值
atomic_sub_and_test(i,v) 從*v中減去i, 如果結果爲0 則返回1;否則,返回0
atomic_inc(v) 把1加到*v
atomic_dec(v) 從*v減 1
atomic_inc_return(v) 把1加到*v,返回*v新值
atomic_dec_return(v) 從*v減1 ,返回* V的新值
2. Linux 中原子位操作
test_bit(nr, addr) 返回*add的第nr位的值
set_bit(nr,addr) 設置*addr的第nr位
clear_bit(nr,addr) 清*addr的第nr位
change_bit(nr, addr) 轉換*addr的第nr位,並返回他的原值
3. Linux讀寫鎖
讀/寫自旋鎖的引入是爲了增加內核的併發能力。只要沒有內核控制路徑對數據結構進行修改,讀/寫自旋鎖就允許多個內核控制路徑同時讀取同一個數據結構,如果一個內核控制路徑相對這個結構進行寫操作,那麼他必須首先獲取讀/寫鎖的寫鎖,寫鎖授權獨佔訪問這個資源。當然,允許對數據結構併發讀可以提高系統性能。
下圖顯示兩個受讀/寫鎖保護的臨界區(C1 和 C2 )。內核控制路徑R0和R1正在同時讀取C1 中數據結構,而W0,正等待獲取寫鎖。內核控制路徑W1 正對C2 中 的數據結構進行寫操作,而R2 和W2 分別等待獲取讀鎖和寫鎖
linux 信號量