Linux內核同步機制spinlock
spin_lock是什么
在平時的工作中,作為開發人員經常碰到這樣的問題:多線程或多進程共享的數據如何進行保護,如果發生進程上下文切換或中斷上下文切換都可能使共享數據發生爭搶問題。這時候就可以考慮用鎖了。如果是進程上下文切換引起的可以考慮用信號量或mutex互斥鎖,但如果發生在中斷上下文,這時候信號量和mutex就無法使用了,因為這兩種鎖機制是可以睡眠的,而中斷上下文又禁止睡眠,這時,spin_lock就是我們最好的選擇了。
- spin_lock是一種死等的鎖機制。當發生資源訪問沖突的時候,可以有兩個選擇:一個是死等,一個是掛起當前進程,調度其他進程執行。
- spin_lock一次只允許一個線程進入共享資源區,信號量可以允許多個線程進入。
- spin_lock執行的時間要短,因為spin_lock是死等鎖,所以在共享資源區的執行時間不能太長,否則會造成CPU的資源浪費。
- spin_lock最重要的一點是可以在中斷上下文執行。
資源競爭
現代CPU一般都是多核多CPU的SMP架構,在這種情況下就有可能出現多個CPU要共同訪問同一個資源的問題,此時就需要對資源進行保護,確保同一個時刻只有一個CPU正在訪問修改資源變量。鎖就是在這種背景下誕生的,鎖的種類有很多,應用場景也不同,本篇我們主要介紹spinlock自旋鎖。
進程上下文
如果一個全局的資源被多個進程上下文訪問,此時,內核是如何執行的呢?對于沒有開啟內核可搶占
選項的內核,所有的系統調用都是串行執行的,并不存在資源競爭的問題。但是。對于現在的大部分內核來說可搶占
選項是開啟的。
假如現在有一個共享資源S,有兩個進程A和B,都需要訪問共享資源S
執行流程大概是這樣的:
進程A訪問資源S的時候發生了中斷,中斷去喚醒優先級更高的進程B,中斷返回的時候,發生進程切換,優先級更高的進程B執行,進程B訪問共享資源S,如果沒有加鎖保護,此時進程A和進程B都訪問了共享資源S,導致程序的執行結果不正確。如果通過spin_lock加鎖保護,進程A訪問共享資源S前獲取spin_lock,此時發生了中斷,優先級更高的進程B開始執行,進程B會去獲取spin_lock,由于spin_lock被進程A所持有,導致進程B獲取spin_lock失敗,進程B會死等直到進程A釋放了spin_lock,然后進程B就可以獲取spin_lock,訪問共享資源S。
中斷上下文
- 進程A運行在CPU0上訪問共享資源S
- 進程B運行在CPU1上訪問共享資源S
- 外部設備發生中斷訪問共享資源S 此時進程A在CPU0上獲取spin_lock開始訪問共享資源S,這時外部設備發生了中斷,并且在CPU1上執行,假如中斷執行了一段時間后被調度到了CPU0上執行,此時會怎樣呢?由于CPU0上進程A已經獲取了spin_lock,現在被中斷上下文打斷,中斷上下文也要獲取spin_lock,那么現在就進入了死胡同也就是死鎖狀態。而進程B在CPU1上也要獲取spin_lock,也會進入死鎖狀態。所有中斷上下文的切換CPU必須禁止本CPU上的中斷。
實現原理
spinlock
結構體定義在頭文件include/linux/spinlock_types.h
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
spinlock
結構體變量的定義有兩種,一種是靜態定義,一種是動態定義。
#define DEFINE_SPINLOCK(x) spinlock_t x = __SPIN_LOCK_UNLOCKED(x) //靜態定義
spinlock_t lock;
spin_lock_init (&lock); //動態定義
spinlock
接口函數介紹,這些函數是驅動編程內核編程的時候會用到的。
位于include/linux/spinlock.h頭文件中
static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock)
{
return &lock- >rlock;
}
#define spin_lock_init(_lock) \\
do { \\
spinlock_check(_lock); \\
raw_spin_lock_init(&(_lock)- >rlock); \\
} while (0)
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock- >rlock);
}
static __always_inline void spin_lock_bh(spinlock_t *lock)
{
raw_spin_lock_bh(&lock- >rlock);
}
static __always_inline int spin_trylock(spinlock_t *lock)
{
return raw_spin_trylock(&lock- >rlock);
}
#define spin_lock_nested(lock, subclass) \\
do { \\
raw_spin_lock_nested(spinlock_check(lock), subclass); \\
} while (0)
#define spin_lock_nest_lock(lock, nest_lock) \\
do { \\
raw_spin_lock_nest_lock(spinlock_check(lock), nest_lock); \\
} while (0)
static __always_inline void spin_lock_irq(spinlock_t *lock)
{
raw_spin_lock_irq(&lock- >rlock);
}
#define spin_lock_irqsave(lock, flags) \\
do { \\
raw_spin_lock_irqsave(spinlock_check(lock), flags); \\
} while (0)
#define spin_lock_irqsave_nested(lock, flags, subclass) \\
do { \\
raw_spin_lock_irqsave_nested(spinlock_check(lock), flags, subclass); \\
} while (0)
static __always_inline void spin_unlock(spinlock_t *lock)
{
raw_spin_unlock(&lock- >rlock);
}
static __always_inline void spin_unlock_bh(spinlock_t *lock)
{
raw_spin_unlock_bh(&lock- >rlock);
}
static __always_inline void spin_unlock_irq(spinlock_t *lock)
{
raw_spin_unlock_irq(&lock- >rlock);
}
static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
raw_spin_unlock_irqrestore(&lock- >rlock, flags);
}
static __always_inline int spin_trylock_bh(spinlock_t *lock)
{
return raw_spin_trylock_bh(&lock- >rlock);
}
static __always_inline int spin_trylock_irq(spinlock_t *lock)
{
return raw_spin_trylock_irq(&lock- >rlock);
}
#define spin_trylock_irqsave(lock, flags) \\
({ \\
raw_spin_trylock_irqsave(spinlock_check(lock), flags); \\
})
static __always_inline int spin_is_locked(spinlock_t *lock)
{
return raw_spin_is_locked(&lock- >rlock);
}
static __always_inline int spin_is_contended(spinlock_t *lock