作者:劉洪濤,華清遠見嵌入式學院講師。
本文主要以2.6.22.6核心分析Linux中spinlock在ARM及X86平台上的實現(不同版本的核心實現形式會有一些差異,但原理大致相同)。此處預設大家已經熟悉了spinlock的使用,重點解釋容易引起迷惑的體繫結構相關的實現部分。
一、spin_lock(lock)的實現
/***include/linux/spinlock.h中***/
#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
//如果配置了SMP或配置自旋鎖調試功能
# include <linux/spinlock_api_smp.h>
#else //如果是單一處理器且不配置自旋鎖調試功能
# 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對代碼檢測的相互關聯的函數定義,第一句表示要增加變數x的計數,增加量為1,第二句則正好相反,這個是用來函數編譯的過程中。如果在代碼中出現了不平衡的狀況,那麼在Sparse的檢測中就會警示。如果要使用Sparse檢測功能就需要安裝sparse工具(參考相關安裝方法),然後編譯核心
#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();
//關閉搶佔
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
//自旋鎖調試用,在沒有定義自旋鎖調試的時候是空函數
_raw_spin_lock(lock);
}
/***include/linux/spinlock.h***/
#ifdef CONFIG_DEBUG_SPINLOCK
extern void _raw_spin_lock(spinlock_t *lock);//在lib/spinlock_debug.c中實現
#else //smp情況
# define _raw_spin_lock(lock) __raw_spin_lock(&(lock)->raw_lock)
3、__raw_spin_lock在ARM處理器上的實現
/******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這個記憶體位址為獨佔訪問
" teq %0, #0/n"
//測試lock_lock是否為0,影響標誌位z
#ifdef CONFIG_CPU_32v6K
" wfene/n"
#endif
" strexeq %0, %2, [%1]/n"
//如果lock_lock是0,並且是獨佔訪問這個記憶體,就向lock->lock裡寫入1,並向tmp返回0,同時清除獨佔標記
" teqeq %0, #0/n"
//如果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"); //狀態寄存器可能會改變
smp_mb();
}
上述代碼關鍵在於LDREX和STREX指令的應用。DREX和STREX指令是在V6以後才出現的,代替了V6以前的swp指令。可以讓bus監控LDREX和STREX指令之間有無其它CPU和DMA來存取過這個地址,若有的話STREX指令的第一個寄存器裡設定為1(動作失敗),若沒有,指令的第一個寄存器裡設定為0(動作成功)。
不僅是自旋鎖用到LDREX和STREX指令,訊號量的實現也是利用LDREX和STREX指令來實現的。
4、__raw_spin_lock在X86處理器上的實現
/******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"
//如果不為負.跳轉到3f.3f後面沒有任何指令,即為退出
"2:/t"
"rep;nop/n/t"
//重複執行nop.nop是x86的小延遲函數
"cmpb $0,%0/n/t"
"jle 2b/n/t"
//如果lock->slock不大於0,跳轉到標號2,即繼續重複執行nop
"jmp 1b/n"
//如果lock->slock大於0,跳轉到標號1,重新判斷鎖的slock成員
"3:/n/t"
: "+m" (lock->slock) : : "memory");
}
在多處理器環境中 LOCK_PREFIX 實際被定義為 “lock”首碼。x86 處理器使用“lock”首碼的方式提供了在指令執行期間對匯流排加鎖的手段。晶片上有一條引線 LOCK,如果在一條彙編指令(ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, XCHG)前加上“lock” 首碼,經過彙編後的機器代碼就使得處理器執行該指令時把引線 LOCK 的電位拉低,從而把匯流排鎖住,這樣其它處理器或使用DMA的外設暫時無法通過同一匯流排訪問記憶體。
jns 彙編指令檢查 EFLAGS 寄存器的 SF(符號)位,如果為 0,說明 slock 原來的值為 1,則線程獲得鎖,然後跳到標籤 3 的位置結束本次函數調用。如果 SF 位為 1,說明 slock 原來的值為 0 或負數,鎖已被佔用。那麼線程轉到標籤 2 處不斷測試 slock 與 0 的大小關係,假如 slock 小於或等於 0,跳轉到標籤 2 的位置繼續忙等待;假如 slock 大於 0,說明鎖已被釋放,則跳轉到標籤 1 的位置重新申請鎖。
二、spin_unlock(lock)的實現
/***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處理器上的實現
/******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處理器上的實現
/***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 函數僅僅執行一條彙編指令:將lock-> slock 置為 1。