簡介自旋鎖
“鎖”的作用就是保護(hù)臨界區(qū)資源,避免不同的CPU同時(shí)訪問相同的變量(或中斷與進(jìn)程同時(shí)訪問相同變量)。非原子變量的賦值,大多數(shù)都不是一個(gè)指令周期能完成的,試想如果CPU1將某變量剛更改了一半,CPU2正好就來讀取了這個(gè)半成品變量,并基于此去做計(jì)算,可能會(huì)造成嚴(yán)重后果。
解決辦法就是CPU1在改寫這個(gè)變量前,先“加鎖”;CPU2訪問這個(gè)變量前也先加鎖,但由于已經(jīng)被加鎖了,所以獲取鎖失敗,CPU2原地等待鎖釋放;CPU1改寫完變量后,“解鎖”;CPU2獲取到鎖后加鎖,然后訪問變量,完成后解鎖。
上面的“鎖”可以是信號量、互斥鎖、自旋鎖等。本文的自旋鎖(spinlock)與其它鎖的最大不同,就是在等待鎖釋放的時(shí)候不會(huì)睡眠,而是在空轉(zhuǎn)(自旋)。信號量和互斥鎖在等待鎖釋放的時(shí)候,會(huì)進(jìn)入睡眠。
不睡眠的好處是:可以在中斷上下文運(yùn)行;另外,對于很快就能獲取到鎖的場景,這種方式效率更高。
不睡眠的壞處是:如果等待鎖釋放的時(shí)間較長,則極其浪費(fèi)CPU資源。
自旋鎖在X86的代碼實(shí)現(xiàn)
/******include/asm-i386/spinlock_types.h***/
typedef struct {
unsigned int slock;
} raw_spinlock_t;
#define __RAW_SPIN_LOCK_UNLOCKED { 1 }
/******include/asm-i386/spinlock.h***/
static inline void __raw_spin_lock(raw_spinlock_t *lock){
asm volatile(
// lock->slock減1
1:LOCK_PREFIX decb %0
//如果不為負(fù).跳轉(zhuǎn)到3f.3f后面沒有任何指令,即為退出
jns 3f
//重復(fù)執(zhí)行nop.nop是x86的小延遲函數(shù)
2:rep nop
cmpb $0,%0
//如果lock->slock不大于0,跳轉(zhuǎn)到標(biāo)號2,即繼續(xù)重復(fù)執(zhí)行nop
jle 2
//如果lock->slock大于0,跳轉(zhuǎn)到標(biāo)號1,重新判斷鎖的slock成員
jmp 1
3:: "+m" (lock->slock) : : "memory");
}
在多處理器環(huán)境中 LOCK_PREFIX 實(shí)際被定義為 “lock”前綴。x86 處理器使用“lock”前綴的方式提供了在指令執(zhí)行期間對總線加鎖的手段。芯片上有一條引線 LOCK,如果在一條匯編指令(ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, XCHG)前加上“lock” 前綴,經(jīng)過匯編后的機(jī)器代碼就使得處理器執(zhí)行該指令時(shí)把引線 LOCK 的電位拉低,從而把總線鎖住,這樣其它處理器或使用DMA的外設(shè)暫時(shí)無法通過同一總線訪問內(nèi)存。
jns 匯編指令檢查 EFLAGS 寄存器的 SF(符號)位,如果為 0,說明 slock 原來的值為 1,則線程獲得鎖,然后跳到標(biāo)簽 3 的位置結(jié)束本次函數(shù)調(diào)用。如果 SF 位為 1,說明 slock 原來的值為 0 或負(fù)數(shù),鎖已被占用。那么線程轉(zhuǎn)到標(biāo)簽 2 處不斷測試 slock 與 0 的大小關(guān)系,假如 slock 小于或等于 0,跳轉(zhuǎn)到標(biāo)簽 2 的位置繼續(xù)忙等待;假如 slock 大于 0,說明鎖已被釋放,則跳轉(zhuǎn)到標(biāo)簽 1 的位置重新申請鎖。