分析Linux中Spinlock在ARM及X86平臺上的實現(xiàn)
作者:劉洪濤,華清遠見嵌入式學院講師。
本文主要以2.6.22.6內(nèi)核分析Linux中spinlock在ARM及X86平臺上的實現(xiàn)(不同版本的內(nèi)核實現(xiàn)形式會有一些差異,但原理大致相同)。此處默認大家已經(jīng)熟悉了spinlock的使用,重點解釋容易引起迷惑的體系結(jié)構(gòu)相關(guān)的實現(xiàn)部分。
一、spin_lock(lock)的實現(xiàn)
/***include/linux/spinlock.h中***/
#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
//如果配置了SMP或配置自旋鎖調(diào)試功能
# include <linux/spinlock_api_smp.h>
#else //如果是單處理器且不配置自旋鎖調(diào)試功能
# include <linux/spinlock_api_up.h>
#endif
……
#define spin_lock(lock) _spin_lock(lock)
1、如果是單處理器
/****include/linux/spinlock_api_up.h****/
#define _spin_lock(lock) __LOCK(lock)
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)
(1)preempt_disable():禁止搶占
(2)__acquire(lock):在include/linux/compiler.h中有定義
#ifdef __CHECKER__
……
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
#else
……
# define __acquires(x)
# define __releases(x)
這是一對用于sparse對代碼檢測的相互關(guān)聯(lián)的函數(shù)定義,第一句表示要增加變量x的計數(shù),增加量為1,第二句則正好相反,這個是用來函數(shù)編譯的過程中。如果在代碼中出現(xiàn)了不平衡的狀況,那么在Sparse的檢測中就會報警。如果要使用Sparse檢測功能就需要安裝sparse工具(參考相關(guān)安裝方法),然后編譯內(nèi)核
#make zImage C=1 (C=1,只檢測新編譯的文件,C=2是查所有文件)
Sparse會定義__CHECKER__,如果你沒有使用sparse工具,__acquire(lock)則定義為空
(3)(void)(lock):通過插入一個變量本身的求值表達式,使編譯器不再報警,如:“variable "lock" is defined but never used”。這種求值不會影響運行時的速度。
2、如果配置了SMP
/****include/linux/spinlock_api_smp.h中****/
void __lockfunc _spin_lock(spinlock_t *lock) __acquires(lock);
/***kernel/spinlock.c***/
void __lockfunc _spin_lock(spinlock_t *lock)
{
preempt_disable();
//關(guān)閉搶占
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
//自旋鎖調(diào)試用,在沒有定義自旋鎖調(diào)試的時候是空函數(shù)
_raw_spin_lock(lock);
}
/***include/linux/spinlock.h***/
#ifdef CONFIG_DEBUG_SPINLOCK
extern void _raw_spin_lock(spinlock_t *lock);//在lib/spinlock_debug.c中實現(xiàn)
#else //smp情況
# define _raw_spin_lock(lock) __raw_spin_lock(&(lock)->raw_lock)
3、__raw_spin_lock在ARM處理器上的實現(xiàn)
/******include/asm-arm/spinlock_types.h***/
typedef struct {
volatile unsigned int lock;
} raw_spinlock_t;
#define __RAW_SPIN_LOCK_UNLOCKED { 0 }
/******include/asm-arm/spinlock.h***/
#if __LINUX_ARM_ARCH__ < 6
#error SMP not supported on pre-ARMv6 CPUs //ARMv6后,才有多核ARM處理器
#endif
……
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
unsigned long tmp;
__asm__ __volatile__(
"1: ldrex %0, [%1]\n"
//取lock->lock放在 tmp里,并且設置&lock->lock這個內(nèi)存地址為獨占訪問
" teq %0, #0\n"
//測試lock_lock是否為0,影響標志位z
#ifdef CONFIG_CPU_32v6K
" wfene\n"
#endif
" strexeq %0, %2, [%1]\n"
//如果lock_lock是0,并且是獨占訪問這個內(nèi)存,就向lock->lock里寫入1,并向tmp返回0,同時清除獨占標記
" teqeq %0, #0\n"
//如果lock_lock是0,并且strexeq返回了0,表示加鎖成功,返回
" bne 1b"
//如果上面的條件(1:lock->lock里不為0,2:strexeq失敗)有一個符合,就在原地打轉(zhuǎn)
: "=&r" (tmp) //%0:輸出放在tmp里,可以是任意寄存器
: "r" (&lock->lock), "r" (1)
//%1:取&lock->lock放在任意寄存器,%2:任意寄存器放入1
: "cc"); //狀態(tài)寄存器可能會改變
smp_mb();
}
上述代碼關(guān)鍵在于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("\n1:\t"
LOCK_PREFIX " ; decb %0\n\t"
// lock->slock減1
"jns 3f\n"
//如果不為負.跳轉(zhuǎn)到3f.3f后面沒有任何指令,即為退出
"2:\t"
"rep;nop\n\t"
//重復執(zhí)行nop.nop是x86的小延遲函數(shù)
"cmpb $0,%0\n\t"
"jle 2b\n\t"
//如果lock->slock不大于0,跳轉(zhuǎn)到標號2,即繼續(xù)重復執(zhí)行nop
"jmp 1b\n"
//如果lock->slock大于0,跳轉(zhuǎn)到標號1,重新判斷鎖的slock成員
"3:\n\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 的位置結(jié)束本次函數(shù)調(diào)用。如果 SF 位為 1,說明 slock 原來的值為 0 或負數(shù),鎖已被占用。那么線程轉(zhuǎn)到標簽 2 處不斷測試 slock 與 0 的大小關(guān)系,假如 slock 小于或等于 0,跳轉(zhuǎn)到標簽 2 的位置繼續(xù)忙等待;假如 slock 大于 0,說明鎖已被釋放,則跳轉(zhuǎn)到標簽 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]\n" // 向lock->lock里寫0,解鎖
#ifdef CONFIG_CPU_32v6K
" mcr p15, 0, %1, c7, c10, 4\n" /* 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。
“本文由華清遠見http://www.embedu.org/index.htm提供”
華清遠見





