分析Linux中Spinlock在ARM及X86平臺上的實現(xiàn)
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
unsigned long tmp;
__asm__ __volatile__(
"1: ldrex %0, [%1]"
//取lock->lock放在 tmp里,并且設置lock->lock這個內(nèi)存地址為獨占訪問
" teq %0, #0"
//測試lock_lock是否為0,影響標志位z
#ifdef CONFIG_CPU_32v6K
" wfene"
#endif
" strexeq %0, %2, [%1]"
//如果lock_lock是0,并且是獨占訪問這個內(nèi)存,就向lock->lock里寫入1,并向tmp返回0,同時清除獨占標記
" teqeq %0, #0"
//如果lock_lock是0,并且strexeq返回了0,表示加鎖成功,返回
" bne 1b"
//如果上面的條件(1:lock->lock里不為0,2:strexeq失敗)有一個符合,就在原地打轉
: "=r" (tmp) //%0:輸出放在tmp里,可以是任意寄存器
: "r" (lock->lock), "r" (1)
//%1:取lock->lock放在任意寄存器,%2:任意寄存器放入1
: "cc"); //狀態(tài)寄存器可能會改變
smp_mb();
}
上述代碼關鍵在于LDREX和STREX指令的應用。DREX和STREX指令是在V6以后才出現(xiàn)的,代替了V6以前的swp指令。可以讓bus監(jiān)控LDREX和STREX指令之間有無其它CPU和DMA來存取過這個地址,若有的話STREX指令的第一個寄存器里設置為1(動作失?。魶]有,指令的第一個寄存器里設置為0(動作成功)。
不僅是自旋鎖用到LDREX和STREX指令,信號量的實現(xiàn)也是利用LDREX和STREX指令來實現(xiàn)的。
4、__raw_spin_lock在X86處理器上的實現(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("1:t"
LOCK_PREFIX " ; decb %0t"
// lock->slock減1
"jns 3f"
//如果不為負.跳轉到3f.3f后面沒有任何指令,即為退出
"2:t"
"rep;nopt"
//重復執(zhí)行nop.nop是x86的小延遲函數(shù)
"cmpb $0,%0t"
"jle 2bt"
//如果lock->slock不大于0,跳轉到標號2,即繼續(xù)重復執(zhí)行nop
"jmp 1b"
//如果lock->slock大于0,跳轉到標號1,重新判斷鎖的slock成員
"3:t"
: "+m" (lock->slock) : : "memory");
}
在多處理器環(huán)境中 LOCK_PREFIX 實際被定義為 “l(fā)ock”前綴。x86 處理器使用“l(fā)ock”前綴的方式提供了在指令執(zhí)行期間對總線加鎖的手段。芯片上有一條引線 LOCK,如果在一條匯編指令(ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, XCHG)前加上“l(fā)ock” 前綴,經(jīng)過匯編后的機器代碼就使得處理器執(zhí)行該指令時把引線 LOCK 的電位拉低,從而把總線鎖住,這樣其它處理器或使用DMA的外設暫時無法通過同一總線訪問內(nèi)存。
jns 匯編指令檢查 EFLAGS 寄存器的 SF(符號)位,如果為 0,說明 slock 原來的值為 1,則線程獲得鎖,然后跳到標簽 3 的位置結束本次函數(shù)調(diào)用。如果 SF 位為 1,說明 slock 原來的值為 0 或負數(shù),鎖已被占用。那么線程轉到標簽 2 處不斷測試 slock 與 0 的大小關系,假如 slock 小于或等于 0,跳轉到標簽 2 的位置繼續(xù)忙等待;假如 slock 大于 0,說明鎖已被釋放,則跳轉到標簽 1 的位置重新申請鎖。
二、spin_unlock(lock)的實現(xiàn)
/***include/linux/spinlock.h***/
#if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) ||
!defined(CONFIG_SMP)
# define spin_unlock(lock) _spin_unlock(lock)
……
#else
# define spin_unlock(lock)
do {__raw_spin_unlock((lock)->raw_lock); __release(lock); } while (0)
1、 如果是單處理器
/****include/linux/spinlock_api_up.h****/
#define _spin_unlock(lock) __UNLOCK(lock)
#define __UNLOCK(lock)
do { preempt_enable(); __release(lock); (void)(lock); } while (0)
完成前文的獲取鎖的逆過程
2、如果配置了SMP
# define spin_unlock(lock)
do {__raw_spin_unlock((lock)->raw_lock); __release(lock); } while (0)
3、__raw_spin_unlock在ARM處理器上的實現(xiàn)
/******include/asm-arm/spinlock.h***/
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
smp_mb();
__asm__ __volatile__(
" str %1, [%0]" // 向lock->lock里寫0,解鎖
#ifdef CONFIG_CPU_32v6K
" mcr p15, 0, %1, c7, c10, 4" /* DSB */
" sev"
#endif
:
: "r" (lock->lock), "r" (0) //%0取lock->lock放在任意寄存器,%1:任意寄存器放入0
: "cc");
}
__raw_spin_unlock只是簡單的給lock->lock里寫0。
4、__raw_spin_unlock在X86處理器上的實現(xiàn)
/***include/asm-i386/spinlock.h***/
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
asm volatile("movb $1,%0" : "+m" (lock->slock) :: "memory");
}
__raw_spin_unlock 函數(shù)僅僅執(zhí)行一條匯編指令:將lock-> slock 置為 1。
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)
評論