關於單CPU,多CPU上的原子操作

所謂原子操作,就是"不可中斷的一個或一系列操作" 。


1.1.硬件級的原子操作:

在單處理器系統(UniProcessor)中,能夠在單條指令中完成的操作都可以認爲是"原子操作",因爲中斷只能發生於指令之間。這也是某些CPU指令系統中引入了test_and_set、test_and_clear等指令用於臨界資源互斥的原因。


在對稱多處理器(Symmetric Multi-Processor)結構中就不同了,由於系統中有多個處理器在獨立地運行,即使能在單條指令中完成的操作也有可能受到干擾。


在x86平臺上,CPU提供了在指令執行期間對總線加鎖的手段。CPU芯片上有一條引線#HLOCK pin,如果彙編語言的程序中在一條指令前面加上前綴"LOCK",經過彙編以後的機器代碼就使CPU在執行這條指令的時候把#HLOCK pin的電位拉低,持續到這條指令結束時放開,從而把總線鎖住,這樣同一總線上別的CPU就暫時不能通過總線訪問內存了,保證了這條指令在多處理器環境中的原子性。


1.2.軟件級的原子操作:

軟件級的原子操作實現依賴於硬件原子操作的支持。

對於Linux而言,內核提供了兩組原子操作接口:一組是針對整數進行操作;另一組是針對單獨的位進行操作。


2.1. 原子整數操作

針對整數的原子操作只能對atomic_t類型的數據處理。這裏沒有使用C語言的int類型,主要是因爲:

1) 讓原子函數只接受atomic_t類型操作數,可以確保原子操作只與這種特殊類型數據一起使用。

2) 使用atomic_t類型確保編譯器不對相應的值進行訪問優化。

3) 使用atomic_t類型可以屏蔽不同體系結構上的數據類型的差異。儘管Linux支持的所有機器上的整型數據都是32位,但是使用


atomic_t的代碼只能將該類型的數據當作24位來使用。這個限制完全是因爲在SPARC體系結構上,原子操作的實現不同於其它體系結構:32位int類型的低8位嵌入了一個鎖,因爲SPARC體系結構對原子操作缺乏指令級的支持,所以只能利用該鎖來避免對原子類型數據的併發訪問。


原子整數操作最常見的用途就是實現計數器。原子整數操作列表在中定義。原子操作通常是內聯函數,往往通過內嵌彙編指令來實現。如果某個函數本來就是原子的,那麼它往往會被定義成一個宏。


在編寫內核時,操作也簡單:

atomic_t use_cnt;

atomic_set(&use_cnt, 2);

atomic_add(4, &use_cnt);

atomic_inc(use_cnt);


2.2. 原子性與順序性


原子性確保指令執行期間不被打斷,要麼全部執行,要麼根本不執行。而順序性確保即使兩條或多條指令出現在獨立的執行線程中,甚至獨立的處理器上,它們本該執行的順序依然要保持。


2.3. 原子位操作


原子位操作定義在文件中。令人感到奇怪的是位操作函數是對普通的內存地址進行操作的。原子位操作在多數情況下是對一個字長的內存訪問,因而位號該位於0-31之間(在64位機器上是0-63之間),但是對位號的範圍沒有限制。


編寫內核代碼,只要把指向了你希望的數據的指針給操作函數,就可以進行位操作了:

unsigned long word = 0;

set_bit(0, &word); /*第0位被設置*/

set_bit(1, &word); /*第1位被設置*/

clear_bit(1, &word); /*第1位被清空*/

change_bit(0, &word); /*翻轉第0位*/



爲什麼關注原子操作?

1)在確認一個操作是原子的情況下,多線程環境裏面,我們可以避免僅僅爲保護這個操作在外圍加上性能開銷昂貴的鎖。

2)藉助於原子操作,我們可以實現互斥鎖。

3)藉助於互斥鎖,我們可以把一些列操作變爲原子操作。


GNU C中x++是原子操作嗎?

答案不是。x++由3條指令完成。x++在單CPU下不是原子操作。


對應3條彙編指令

movl x, %eax

addl $1, %eax

movl %eax, x


在vc2005下對應

++x;

004232FA mov eax,dword ptr [x]

004232FD add eax,1

00423300 mov dword ptr [x],eax

仍然是3條指令。


所以++x,x++等都不是原子操作。因其步驟包括了從內存中取x值放入寄存器,加寄存器,把值寫入內存三個指令。


如何實現x++的原子性?


在單處理器上,如果執行x++時,禁止多線程調度,就可以實現原子。因爲單處理的多線程併發是僞併發。

在多處理器上,需要藉助cpu提供的Lock功能。鎖總線。讀取內存值,修改,寫回內存三步期間禁止別的CPU訪問總線。同時我估計使


用Lock指令鎖總線的時候,OS也不會把當前線程調度走了。要是調走了,那就麻煩了。


在多處理器系統中存在潛在問題的原因是:

不使用LOCK指令前綴鎖定總線的話,在一次內存訪問週期中有可能其他處理器會產生異常或中斷,而在異常處理中有可能會修改尚未寫入的地址,這樣當INC操作完成後會產生無效數據(覆蓋了前面的修改)。


spinlock 用於CPU同步,它的實現是基於CPU鎖定數據總線的指令。

當某個CPU鎖住數據總線後,它讀一個內存單元(spinlock_t)來判斷這個spinlock 是否已經被別的CPU鎖住。如果否,它寫進一個特定值,表示鎖定成功,然後返回。如果是,它會重複以上操作直到成功,或者spin次數超過一個設定值。鎖定數據總線的指令只能保證一個機器指令內,CPU獨佔數據總線。

單CPU當然能用spinlock,但實現上無需鎖定數據總線。


spinlock在鎖定的時候,如果不成功,不會睡眠,會持續的嘗試,單cpu的時候spinlock會讓其它process動不了。


原文地址:http://software.intel.com/zh-cn/blogs/2010/01/14/cpucpu/



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