在多進(jìn)程(線(xiàn)程)訪問(wèn)資源時(shí),能夠確保所有其他的進(jìn)程(線(xiàn)程)都不在同一時(shí)間內(nèi)訪問(wèn)相同的資源。
原子操作:UP和SMP的異同
-----------------------------------------------------------
原子操作是不可分割的,在執(zhí)行完畢不會(huì)被任何其它任務(wù)或事件中斷。在單處理器系統(tǒng)(UniProcessor)中,能夠在單條指令中完成的操作都可以認(rèn)為是" 原子操作",因?yàn)橹袛嘀荒馨l(fā)生于指令之間。這也是某些CPU指令系統(tǒng)中引入了test_and_set、test_and_clear等指令用于臨界資源互斥的原因。但是,在對(duì)稱(chēng)多處理器(Symmetric Multi-Processor)結(jié)構(gòu)中就不同了,由于系統(tǒng)中有多個(gè)處理器在獨(dú)立地運(yùn)行,即使能在單條指令中完成的操作也有可能受到干擾。我們以decl (遞減指令)為例,這是一個(gè)典型的"讀-改-寫(xiě)"過(guò)程,涉及兩次內(nèi)存訪問(wèn)。設(shè)想在不同CPU運(yùn)行的兩個(gè)進(jìn)程都在遞減某個(gè)計(jì)數(shù)值,可能發(fā)生的情況是:
1. CPU A(CPU A上所運(yùn)行的進(jìn)程,以下同)從內(nèi)存單元把當(dāng)前計(jì)數(shù)值(2)裝載進(jìn)它的寄存器中;
2. CPU B從內(nèi)存單元把當(dāng)前計(jì)數(shù)值(2)裝載進(jìn)它的寄存器中。
3. CPU A在它的寄存器中將計(jì)數(shù)值遞減為1;
4. CPU B在它的寄存器中將計(jì)數(shù)值遞減為1;
5. CPU A把修改后的計(jì)數(shù)值(1)寫(xiě)回內(nèi)存單元。
6. CPU B把修改后的計(jì)數(shù)值(1)寫(xiě)回內(nèi)存單元。
我們看到,內(nèi)存里的計(jì)數(shù)值應(yīng)該是0,然而它卻是1。如果該計(jì)數(shù)值是一個(gè)共享資源的引用計(jì)數(shù),每個(gè)進(jìn)程都在遞減后把該值與0進(jìn)行比較,從而確定是否需要釋放該共享資源。這時(shí),兩個(gè)進(jìn)程都去掉了對(duì)該共享資源的引用,但沒(méi)有一個(gè)進(jìn)程能夠釋放它--兩個(gè)進(jìn)程都推斷出:計(jì)數(shù)值是1,共享資源仍然在被使用。
原子性不可能由軟件單獨(dú)保證--必須需要硬件的支持,因此是和架構(gòu)相關(guān)的。在x86 平臺(tái)上,CPU提供了在指令執(zhí)行期間對(duì)總線(xiàn)加鎖的手段。CPU芯片上有一條引線(xiàn)#HLOCK pin,如果匯編語(yǔ)言的程序中在一條指令前面加上前綴"LOCK",經(jīng)過(guò)匯編以后的機(jī)器代碼就使CPU在執(zhí)行這條指令的時(shí)候把#HLOCK pin的電位拉低,持續(xù)到這條指令結(jié)束時(shí)放開(kāi),從而把總線(xiàn)鎖住,這樣同一總線(xiàn)上別的CPU就暫時(shí)不能通過(guò)總線(xiàn)訪問(wèn)內(nèi)存了,保證了這條指令在多處理器環(huán)境中的原子性。
Linux內(nèi)核中的原子操作
-----------------------------------------------------------
原子操作大部分使用匯編語(yǔ)言實(shí)現(xiàn),因?yàn)?span lang="EN-US">c語(yǔ)言并不能實(shí)現(xiàn)這樣的操作。
* 在x86的原子操作實(shí)現(xiàn)代碼中,定義了LOCK宏,這個(gè)宏可以放在隨后的內(nèi)聯(lián)匯編指令之前。如果是SMP,LOCK宏被擴(kuò)展為lock指令;否則被定義為空 -- 單CPU無(wú)需防止其它CPU的干擾,鎖內(nèi)存總線(xiàn)完全是在浪費(fèi)時(shí)間。
#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif
* typedef struct { volatile int counter; } atomic_t;
在所有支持的體系結(jié)構(gòu)上原子類(lèi)型atomic_t都保存一個(gè)int值。在x86的某些處理器上,由于工作方式的原因,原子類(lèi)型能夠保證的可用范圍只有24位。volatile是一個(gè)類(lèi)型描述符,要求編譯器不要對(duì)其描述的對(duì)象作優(yōu)化處理,對(duì)它的讀寫(xiě)都需要從內(nèi)存中訪問(wèn)。
* #define ATOMIC_INIT(i) { (i) }
用于在定義原子變量時(shí),初始化為指定的值。如:
static atomic_t count = ATOMIC_INIT(1);
* static __inline__ void atomic_add(int i, atomic_t *v)
----------------------------------------
將v指向的原子變量加上i。該函數(shù)不關(guān)心原子變量的新值,返回void類(lèi)型。
在下面的實(shí)現(xiàn)中,使用了帶有C/C++表達(dá)式的內(nèi)聯(lián)匯編代碼,格式如下(參考《AT&T ASM Syntax》):
__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
__asm__ __volatile__指示編譯器原封不動(dòng)保留表達(dá)式中的匯編指令系列,不要考慮優(yōu)化處理。涉及的約束還包括:
1. 等號(hào)約束(=):只能用于輸出操作表達(dá)式約束,說(shuō)明括號(hào)內(nèi)的左值表達(dá)式v->counter是write-only的。
2. 內(nèi)存約束(m):表示使用不需要借助寄存器,直接使用內(nèi)存方式進(jìn)行輸入或輸出。
3. 立即數(shù)約束(i):表示輸入表達(dá)式是一個(gè)立即數(shù)(整數(shù)),不需要借助任何寄存器。
4. 寄存器約束(r):表示使用一個(gè)通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl和%edx/%dx/%dl中選取一個(gè)合適的。
{
__asm__ __volatile__(
LOCK "addl %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}
* static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
----------------------------------------
從v 指向的原子變量減去i,并測(cè)試是否為0。若為0,返回真,否則返回假。由于x86的subl指令會(huì)在結(jié)果為0時(shí)設(shè)置CPU的zero標(biāo)志位,而且這個(gè)標(biāo)志位是CPU私有的,不會(huì)被其它CPU影響。因此,可以執(zhí)行一次加鎖的減操作,再根據(jù)CPU的zero標(biāo)志位來(lái)設(shè)置本地變量c,并相應(yīng)返回。
{
unsigned char c;
__asm__ __volatile__(
LOCK "subl %2,%0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"ir" (i), "m" (v->counter) : "memory");
return c;
}
------------------------------------
#define atomic_read(v) ((v)->counter)
讀取v指向的原子變量的值。由于該操作本身就是原子的,只需要一次內(nèi)存訪問(wèn)就能完成,因此定義為一個(gè)宏,并用C代碼實(shí)現(xiàn)。
#define atomic_set(v,i) (((v)->counter) = (i))
設(shè)置v指向的原子變量的值為i。由于該操作本身就是原子的,只需要一次內(nèi)存訪問(wèn)就能完成,因此定義為一個(gè)宏,并用C代碼實(shí)現(xiàn)。
static __inline__ void atomic_sub(int i, atomic_t *v)
從v指向的原子變量減去i。
static __inline__ void atomic_inc(atomic_t *v)
遞增v指向的原子變量。
static __inline__ void atomic_dec(atomic_t *v)
遞減v指向的原子變量。
static __inline__ int atomic_dec_and_test(atomic_t *v)
遞減v指向的原子變量,并測(cè)試是否為0。若為0,返回真,否則返回假。
static __inline__ int atomic_inc_and_test(atomic_t *v)
遞增v指向的原子變量,并測(cè)試是否為0。若為0,返回真,否則返回假。
static __inline__ int atomic_add_negative(int i, atomic_t *v)
將v指向的原子變量加上i,并測(cè)試結(jié)果是否為負(fù)。若為負(fù),返回真,否則返回假。這個(gè)操作用于實(shí)現(xiàn)semaphore。