關(guān)于單CPU,多CPU上的原子操作 轉(zhuǎn)自
http://software.intel.com/zh-cn/blogs/2010/01/14/cpucpu/?cid=sw:prccsdn956
所謂原子操作,就是"不可中斷的一個(gè)或一系列操作" 。
硬件級(jí)的原子操作:
在單處理器系統(tǒng)(UniProcessor)中,能夠在單條指令中完成的操作都可以認(rèn)為是" 原子操作",因?yàn)橹袛嘀荒馨l(fā)生于指令之間。這也是某些CPU指令系統(tǒng)中引入了test_and_set、test_and_clear等指令用于臨界資源互斥的原因。
在對(duì)稱多處理器(Symmetric Multi-Processor)結(jié)構(gòu)中就不同了,由于系統(tǒng)中有多個(gè)處理器在獨(dú)立地運(yùn)行,即使能在單條指令中完成的操作也有可能受到干擾。
在x86 平臺(tái)上,CPU提供了在指令執(zhí)行期間對(duì)總線加鎖的手段。CPU芯片上有一條引線#HLOCK
pin,如果匯編語言的程序中在一條指令前面加上前綴"LOCK",經(jīng)過匯編以后的機(jī)器代碼就使CPU在執(zhí)行這條指令的時(shí)候把#HLOCK
pin的電位拉低,持續(xù)到這條指令結(jié)束時(shí)放開,從而把總線鎖住,這樣同一總線上別的CPU就暫時(shí)不能通過總線訪問內(nèi)存了,保證了這條指令在多處理器環(huán)境中
的原子性。
軟件級(jí)的原子操作:
軟件級(jí)的原子操作實(shí)現(xiàn)依賴于硬件原子操作的支持。
對(duì)于linux而言,內(nèi)核提供了兩組原子操作接口:一組是針對(duì)整數(shù)進(jìn)行操作;另一組是針對(duì)單獨(dú)的位進(jìn)行操作。
2.1. 原子整數(shù)操作
針對(duì)整數(shù)的原子操作只能對(duì)atomic_t類型的數(shù)據(jù)處理。這里沒有使用C語言的int類型,主要是因?yàn)椋?/p>
1) 讓原子函數(shù)只接受atomic_t類型操作數(shù),可以確保原子操作只與這種特殊類型數(shù)據(jù)一起使用
2) 使用atomic_t類型確保編譯器不對(duì)相應(yīng)的值進(jìn)行訪問優(yōu)化
3)
使用atomic_t類型可以屏蔽不同體系結(jié)構(gòu)上的數(shù)據(jù)類型的差異。盡管Linux支持的所有機(jī)器上的整型數(shù)據(jù)都是32位,但是使用atomic_t的代
碼只能將該類型的數(shù)據(jù)當(dāng)作24位來使用。這個(gè)限制完全是因?yàn)樵赟PARC體系結(jié)構(gòu)上,原子操作的實(shí)現(xiàn)不同于其它體系結(jié)構(gòu):32位int類型的低8位嵌入了
一個(gè)鎖,因?yàn)镾PARC體系結(jié)構(gòu)對(duì)原子操作缺乏指令級(jí)的支持,所以只能利用該鎖來避免對(duì)原子類型數(shù)據(jù)的并發(fā)訪問。
原子整數(shù)操作最常見的用途就是實(shí)現(xiàn)計(jì)數(shù)器。原子整數(shù)操作列表在中定義。原子操作通常是內(nèi)斂函數(shù),往往通過內(nèi)嵌匯編指令來實(shí)現(xiàn)。如果某個(gè)函數(shù)本來就是原子的,那么它往往會(huì)被定義成一個(gè)宏。
在編寫內(nèi)核時(shí),操作也簡(jiǎn)單:
atomic_t use_cnt;
atomic_set(&use_cnt, 2);
atomic_add(4, &use_cnt);
atomic_inc(use_cnt);
2.2. 原子性與順序性
原子性確保指令執(zhí)行期間不被打斷,要么全部執(zhí)行,要么根本不執(zhí)行。而順序性確保即使兩條或多條指令出現(xiàn)在獨(dú)立的執(zhí)行線程中,甚至獨(dú)立的處理器上,它們本該執(zhí)行的順序依然要保持。
2.3. 原子位操作
原子位操作定義在文件中。令人感到奇怪的是位操作函數(shù)是對(duì)普通的內(nèi)存地址進(jìn)行操作的。原子位操作在多數(shù)情況下是對(duì)一個(gè)字長(zhǎng)的內(nèi)存訪問,因而位號(hào)該位于0-31之間(在64位機(jī)器上是0-63之間),但是對(duì)位號(hào)的范圍沒有限制。
編寫內(nèi)核代碼,只要把指向了你希望的數(shù)據(jù)的指針給操作函數(shù),就可以進(jìn)行位操作了:
unsigned long word = 0;
set_bit(0, &word); /*第0位被設(shè)置*/
set_bit(1, &word); /*第1位被設(shè)置*/
clear_bit(1, &word); /*第1位被清空*/
change_bit(0, &word); /*翻轉(zhuǎn)第0位*/
為什么關(guān)注原子操作?
1)在確認(rèn)一個(gè)操作是原子的情況下,多線程環(huán)境里面,我們可以避免僅僅為保護(hù)這個(gè)操作在外圍加上性能開銷昂貴的鎖。
2)借助于原子操作,我們可以實(shí)現(xiàn)互斥鎖。
3)借助于互斥鎖,我們可以把一些列操作變?yōu)樵硬僮鳌?/p>
GNU C中x++是原子操作嗎?
答案不是。x++由3條指令完成。x++在單CPU下不是原子操作。
對(duì)應(yīng)3條匯編指令
movl x, %eax
addl $1, %eax
movl %eax, x
在vc2005下對(duì)應(yīng)
++x;
004232FA mov eax,dword ptr [x]
004232FD add eax,1
00423300 mov dword ptr [x],eax
仍然是3條指令。
所以++x,x++等都不是原子操作。因其步驟包括了從內(nèi)存中取x值放入寄存器,加寄存器,把值寫入內(nèi)存三個(gè)指令。
如何實(shí)現(xiàn)x++的原子性?
在單處理器上,如果執(zhí)行x++時(shí),禁止多線程調(diào)度,就可以實(shí)現(xiàn)原子。因?yàn)閱翁幚淼亩嗑€程并發(fā)是偽并發(fā)。
在多處理器上,需要借助cpu提供的Lock功能。鎖總線。讀取內(nèi)存值,修改,寫回內(nèi)存三步期間禁止別的CPU訪問總線。同時(shí)我估計(jì)使用Lock指令鎖總線的時(shí)候,OS也不會(huì)把當(dāng)前線程調(diào)度走了。要是調(diào)走了,那就麻煩了。
在多處理器系統(tǒng)中存在潛在問題的原因是:
不使用LOCK指令前綴鎖定總線的話,在一次內(nèi)存訪問周期中有可能其他處理器會(huì)產(chǎn)生異?;蛑袛啵诋惓L幚碇杏锌赡軙?huì)修改尚未寫入的地址,這樣當(dāng)INC操作完成后會(huì)產(chǎn)生無效數(shù)據(jù)(覆蓋了前面的修改)。
spinlock 用于CPU同步, 它的實(shí)現(xiàn)是基于CPU鎖定數(shù)據(jù)總線的指令.
當(dāng)某個(gè)CPU鎖住數(shù)據(jù)總線后,
它讀一個(gè)內(nèi)存單元(spinlock_t)來判斷這個(gè)spinlock 是否已經(jīng)被別的CPU鎖住. 如果否, 它寫進(jìn)一個(gè)特定值, 表示鎖定成功,
然后返回. 如果是, 它會(huì)重復(fù)以上操作直到成功, 或者spin次數(shù)超過一個(gè)設(shè)定值. 鎖定數(shù)據(jù)總線的指令只能保證一個(gè)機(jī)器指令內(nèi),
CPU獨(dú)占數(shù)據(jù)總線.
單CPU當(dāng)然能用spinlock, 但實(shí)現(xiàn)上無需鎖定數(shù)據(jù)總線.
spinlock在鎖定的時(shí)候,如果不成功,不會(huì)睡眠,會(huì)持續(xù)的嘗試,單cpu的時(shí)候spinlock會(huì)讓其它process動(dòng)不了.
from:
http://blog.csdn.net/microtong/archive/2010/01/20/5211750.aspx
posted on 2010-04-03 21:05
chatler 閱讀(1312)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
C++_BASIS