- Spinlock 是內(nèi)核中提供的一種比較常見的鎖機(jī)制,自旋鎖是“原地等待”的方式解決資源沖突的,即恤筛,一個線程獲取了一個自旋鎖后芹橡,另外一個線程期望獲取該自旋鎖毒坛,獲取不到,只能夠原地“打轉(zhuǎn)”(忙等待)林说。由于自旋鎖的這個忙等待的特性,注定了它使用場景上的限制 —— 自旋鎖不應(yīng)該被長時間的持有(消耗 CPU 資源)腿箩。
- 為了避免因為調(diào)度可能自旋的線程而浪費CPU豪直,可以嘗試獲取從運行隊列移出的其他線程持有的鎖,只要持有自旋鎖的代碼正在運行度秘,內(nèi)核就會禁止搶占顶伞。禁止搶占可以防止自旋鎖持有者被移出運行隊列饵撑,這會導(dǎo)致等待進(jìn)程長時間自旋而消耗CPU剑梳。
- 只要有一個任務(wù)持有自旋鎖,其他任務(wù)就可能在等待的時候自旋滑潘。用自旋鎖時垢乙,必須確保不會長時間持有它∮锫保可能有人會說在循環(huán)中自旋所浪費CPU時間,比線程進(jìn)入睡眠粹舵,上下文切換到其他線程或進(jìn)程,然后再喚醒所浪費的CPU更好一些巴席。在一個處理器上自旋意味著在該處理器上不能再運行其他任何任務(wù)漾唉;在單核機(jī)器上使用自旋鎖是沒有任何意義的赵刑。最佳情況下场刑,系統(tǒng)可能會變慢,最糟情況下屎勘,和互斥鎖一樣會造成死鎖概漱。正是因為這個原因瓤摧,內(nèi)核在處理單個處理器上的spin_lock(spinlock_t *lock)調(diào)用時將禁止搶占照弥。在單個處理器(核)系統(tǒng)上这揣,應(yīng)該使用spin_lock_irqsave()和spin_unlock_ irqrestore()给赞,它們分別禁用處理器上中斷片迅,防止中斷并發(fā)皆辽。
- 由于事先并不知道所寫驅(qū)動程序運行在什么系統(tǒng)上柑蛇,因此建議使用spin_lock_irqsave (spinlock_t *lock, unsigned long flags)獲取自旋鎖,該函數(shù)會在獲取自旋鎖之前驱闷,禁止當(dāng)前處理器(調(diào)用該函數(shù)的處理器)上中斷耻台。spin_lock_irqsave在內(nèi)部調(diào)用local_irq_save (flags)和preempt_disable(),前者是一個依賴于體系結(jié)構(gòu)的函數(shù)空另,用于保存IRQ狀態(tài)盆耽,后者禁止在相關(guān)CPU上發(fā)生搶占。然后應(yīng)該用spin_unlock_irqrestore()釋放鎖痹换,它執(zhí)行的操作與我們前面列舉的相反征字。
自旋鎖的特點
1.當(dāng)發(fā)生訪問資源沖突的時候,可以有兩個選擇:一個是死等娇豫,一個是掛起當(dāng)前進(jìn)程匙姜,調(diào)度其他進(jìn)程執(zhí)行。spin lock是一種死等的機(jī)制.
2.只允許一個thread進(jìn)入冯痢。semaphore可以允許多個thread進(jìn)入氮昧,spin lock不行框杜,一次只能有一個thread獲取鎖并進(jìn)入臨界區(qū),其他的thread都是在門口不斷的嘗試袖肥。
3.臨界區(qū)執(zhí)行時間要短咪辱,臨界區(qū)執(zhí)行時間過長,等待該自旋鎖空等的時間越長椎组,浪費CPU資源油狂。
4.可以在中斷上下文執(zhí)行。由于不睡眠寸癌,因此spin lock可以在中斷上下文中適用专筷。
自旋鎖的定義
struct liblockdep_pthread_mutex {
pthread_mutex_t mutex;
struct lock_class_key key;
struct lockdep_map dep_map;
};
typedef struct liblockdep_pthread_mutex liblockdep_pthread_mutex_t;
#define pthread_mutex_t liblockdep_pthread_mutex_t
#define spinlock_t pthread_mutex_t
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
spin_lock的調(diào)用關(guān)系
spin_lock
|
+ -----> raw_spin_lock
|
+------> _raw_spin_lock
|
+--------> __raw_spin_lock
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
spin_lock_irq的調(diào)用關(guān)系
spin_lock_irq
|
+ -----> raw_spin_lock_irq
|
+------> _raw_spin_lock_irq
|
+--------> __raw_spin_lock_irq
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
local_irq_disable();
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
spin_trylock最終調(diào)用的是__raw_spin_trylock,成功返回1蒸苇,獲取失敗返回0
static inline int __raw_spin_trylock(raw_spinlock_t *lock)
{
preempt_disable();
if (do_raw_spin_trylock(lock)) {
spin_acquire(&lock->dep_map, 0, 1, _RET_IP_);
return 1;
}
preempt_enable();
return 0;
}
自旋鎖的使用
1 定義自旋鎖變量 spinlock_t testlock磷蛹;
2 初始化鎖 spin_lock_init(&testlock);
以上兩步也可以通過DEFINE_SPINLOCK(testlock)來實現(xiàn)
3 獲取鎖 spin_lock/spin_lock_irq/spin_lock_irqsave
4 釋放鎖 spin_unlock/spin_unlock_irq/spin_unlock_restore
實例
drivers/input/keyboard/gpio_keys.c
struct gpio_button_data {
......
spinlock_t lock;
......
};
spin_lock_init(&bdata->lock);
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
struct input_dev *input = bdata->input;
unsigned long flags;
BUG_ON(irq != bdata->irq);
spin_lock_irqsave(&bdata->lock, flags);
if (!bdata->key_pressed) {
if (bdata->button->wakeup)
pm_wakeup_event(bdata->input->dev.parent, 0);
input_event(input, EV_KEY, *bdata->code, 1);
input_sync(input);
if (!bdata->release_delay) {
input_event(input, EV_KEY, *bdata->code, 0);
input_sync(input);
goto out;
}
bdata->key_pressed = true;
}
if (bdata->release_delay)
hrtimer_start(&bdata->release_timer,
ms_to_ktime(bdata->release_delay),
HRTIMER_MODE_REL_HARD);
out:
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED;
}
drivers/cpuidle/cpuidle-ux500.c
void nv50_crc_handle_vblank(struct nv50_head *head)
{
......
/*
* We don't lose events if we aren't able to report CRCs until the
* next vblank, so only report CRCs if the locks we need aren't
* contended to prevent missing an actual vblank event
*/
if (!spin_trylock(&crc->lock))
return;
if (!crc->src)
goto out;
out:
spin_unlock(&crc->lock);
}