本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/yunsongice/archive/2010/05/18/5605264.aspx
加鎖(locking)是一種廣泛應用的同步技術。當內核控制路徑必須訪問共享數據結構或進入臨界區時,就需要為自己獲取一把“鎖”。由鎖機制保護的資源非常類似於限制於房間內的資源,當某人進入房間時,就把門鎖上。如果內核控制路徑希望訪問資源,就試圖獲取鑰匙“打開門”。當且僅當資源空閑時,它才能成功。然后,只要它還想使用這個資源,門就依然鎖着。當內核控制路徑釋放了鎖時,門就打開,另一個內核控制路徑就可以進入房間。
Linux鎖的應用之一在多處理器環境中,取名叫自旋鎖(spin lock)。如果內核控制路徑發現自旋鎖“開着”,就獲取鎖並繼續自己的執行。相反,如果內核控制路徑發現鎖由運行在另一個CPU上的內核控制路徑“鎖着”,就在周圍“旋轉”,反復執行一條緊湊的循環指令,直到鎖被釋放。
自旋鎖的循環指令表示“忙等”。即使等待的內核控制路徑無事可做(除了浪費時間),它也在CPU上保持運行。不過,自旋鎖通常非常方便,因為很多內核資源只鎖1毫秒的時間片段;所以說,等待自旋鎖的釋放不會消耗太多CPU的時間。
一般來說,由自旋鎖所保護的每個臨界區都是禁止內核搶占的。在單處理器系統上,這種鎖本身並不起鎖的作用,自旋鎖技術僅僅是用來禁止或啟用內核搶占。請注意,在自旋鎖忙等期間,因為並沒有進入臨界區,所以內核搶占還是有效的,因此,等待自旋鎖釋放的進程有可能被更高優先級的所取代。這種設計是合理的,因為不能因為占用CPU太久而使系統死鎖。
在Linux中,每個自旋鎖都用spinlock_t結構表示:
typedef struct {
raw_spinlock_t raw_lock;
#if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} spinlock_t;
typedef struct {
volatile unsigned int slock;
} raw_spinlock_t;
其中包含兩個重要的字段意義如下:
slock:該字段表示自旋鎖的狀態:值為1表示“未加鎖”狀態,而任何負數和0都表示“加鎖”狀態。
break_lock:表示進程正在忙等自旋鎖(只在內核支持SMP和內核搶占的情況下使用該標志)。
內核提供六個宏用於初始化、測試及設置自旋鎖。所有這些宏都是基於原子操作的,這樣可以保證即使有多個運行在不同CPU上的進程試圖同時修改自旋鎖,自旋鎖也能夠被正確地更新。
1、spin_lock_init —— 初始化自旋鎖,並把自旋鎖的lock->raw_lock置為1(未鎖)
# define spin_lock_init(lock) \
do { \
static struct lock_class_key __key; \
\
__spin_lock_init((lock), #lock, &__key); \
} while (0)
void __spin_lock_init(spinlock_t *lock, const char *name,
struct lock_class_key *key)
{
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/*
* Make sure we are not reinitializing a held lock:
*/
debug_check_no_locks_freed((void *)lock, sizeof(*lock));
lockdep_init_map(&lock->dep_map, name, key, 0);
#endif
lock->raw_lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
lock->magic = SPINLOCK_MAGIC;
lock->owner = SPINLOCK_OWNER_INIT;
lock->owner_cpu = -1;
}
#define __RAW_SPIN_LOCK_UNLOCKED { 1 }
#define SPINLOCK_MAGIC 0xdead4ead
#define SPINLOCK_OWNER_INIT ((void *)-1L)
2、spin_unlock —— 把自旋鎖置為1(未鎖)
#if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) || \
!defined(CONFIG_SMP)
# define spin_unlock(lock) _spin_unlock(lock)
#else //我們還是重點關注后面的吧
# define spin_unlock(lock) __raw_spin_unlock(&(lock)->raw_lock)
#endif
void __lockfunc _spin_unlock(spinlock_t *lock)
{
spin_release(&lock->dep_map, 1, _RET_IP_);
_raw_spin_unlock(lock);
preempt_enable();
}
# define _raw_spin_unlock(lock) __raw_spin_unlock(&(lock)->raw_lock)
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
__asm__ __volatile__(
__raw_spin_unlock_string
);
}
#define __raw_spin_unlock_string \
"movb $1,%0" \
:"+m" (lock->slock) : : "memory"
spin_unlock宏釋放以前獲得的自旋鎖,上面的代碼本質上執行下列匯編語言指令:
movb $1, slp->slock
並在隨后調用preempt_enable()(如果不支持內核搶占,preempt_enable()什么都做)。注意,因為現在的80x86微處理器總是原子地執行內存中的只寫訪問,所以不用lock字節。
3、spin_unlock_wait —— 等待,直到自旋鎖變為1(未鎖)
#define spin_unlock_wait(lock) __raw_spin_unlock_wait(&(lock)->raw_lock)
#define __raw_spin_unlock_wait(lock) \
do { while (__raw_spin_is_locked(lock)) cpu_relax(); } while (0)
#define __raw_spin_is_locked(x) \
(*(volatile signed char *)(&(x)->slock) <= 0) //如果大於0則為真,表示未鎖,則跳出while循環
#define cpu_relax() rep_nop() //在循環中執行一條空指令:
static inline void rep_nop(void)
{
__asm__ __volatile__("rep;nop": : :"memory");
}
4、spin_is_locked( ) —— 如果自旋鎖被置為1(未鎖),返回0;否則,返回1
#define spin_is_locked(lock) __raw_spin_is_locked(&(lock)->raw_lock)
#define __raw_spin_is_locked(x) \
(*(volatile signed char *)(&(x)->slock) <= 0)
5、spin_trylock( ) —— 把自旋鎖置為0(鎖上),如果原來鎖的值是1,則返回1;否則,返回0
#define spin_trylock(lock) __cond_lock(_spin_trylock(lock))
int __lockfunc _spin_trylock(spinlock_t *lock)
{
preempt_disable();
if (_raw_spin_trylock(lock)) {
spin_acquire(&lock->dep_map, 0, 1, _RET_IP_);
return 1;
}
preempt_enable();
return 0;
}
6、spin_lock —— 加鎖:循環,直到自旋鎖變為1(未鎖),然后,把自旋鎖置為0(鎖上)
spin_lock是最重要的一個宏。首先,我們看到在include/spinlock.h頭文件里,有:
#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
# include <linux/spinlock_api_smp.h> //多處理器情況
#else
# include <linux/spinlock_api_up.h> //單處理器情況
#endif
#define spin_lock(lock) _spin_lock(lock)
#ifdef __LINUX_SPINLOCK_API_UP_H
#define _spin_lock(lock) __LOCK(lock) //單處理器情況
#else
注意,該代碼上邊有一句#if !defined(CONFIG_PREEMPT) || !defined(CONFIG_SMP) || \
defined(CONFIG_DEBUG_LOCK_ALLOC)
別去管它,因為上邊的英文注釋寫得很清楚了,這句代碼的意思是即使沒有定義內核搶占或SMP,或者是自旋鎖調試,只要lockdep激活了,也就是我們在剛才spinlock_t定義的那里看到的#ifdef CONFIG_DEBUG_LOCK_ALLOC,都會假設在整個鎖的調試期間保持關中斷。這句#if就是這個意思,千萬別理解成沒有定義內核搶占、SMP或自旋鎖調試了,切記切記。
/*
* If lockdep is enabled then we use the non-preemption spin-ops
* even on CONFIG_PREEMPT, because lockdep assumes that interrupts are
* not re-enabled during lock-acquire (which the preempt-spin-ops do):
*/
//多處理器情況,並且允許內核搶占:
void __lockfunc _spin_lock(spinlock_t *lock)
{
//禁止搶占
preempt_disable();
//這個函數在沒有定義自旋鎖調試的時候是空函數,我們不去管它
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
//相當於_raw_spin_lock(lock)
LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
}
在沒有定義自旋鎖調試的時候,LOCK_CONTENDED宏定義如下
#define LOCK_CONTENDED(_lock, try, lock) \
lock(_lock)
我們看到其實就是調用_raw_spin_lock宏(位於include/linux/spinlock.h):
# define _raw_spin_lock(lock) __raw_spin_lock(&(lock)->raw_lock)
於是,定位到include/asm-i386/Spinlock.h
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
asm(__raw_spin_lock_string : "+m" (lock->slock) : : "memory");
}
展開:
#define __raw_spin_lock_string \
"\n1:\t" \
//原子減,如果不為負,跳轉到3f,3f后面沒有任何指令,即為退出
LOCK_PREFIX " ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
//重復執行nop,nop是x86的小延遲函數,執行空操作
"rep;nop\n\t" \
//比較0與lock->slock的值,如果lock->slock不大於0,跳轉到標號2,即繼續重復執行nop
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
//如果lock->slock大於0,跳轉到標號1,重新判斷鎖的slock成員
"jmp 1b\n" \
"3:\n\t"
在上面的函數中,大家可能對"jmp 1b\n“比較難以理解。在我們一般的觀念里,獲得一個鎖,將其值減1;釋放鎖時將其值加1;實際上在自旋鎖的實現中lock->slock只有兩個可能值,一個是0. 一個是1。釋放鎖的時候並不是將lock->slock加1,而是將其賦為1。請看在前面的自旋鎖釋放代碼spin_unlock中的詳細分析。