轉自:http://blog.csdn.net/wh_19910525/article/details/11536279
自旋鎖的初衷:在短期間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖重新可用的期間進行自旋(特別浪費處理器時間),所以自旋鎖不應該被持有時間過長。如果需要長時間鎖定的話, 最好使用信號量。
單處理器的自旋鎖:
- 首先,自旋鎖的目的如果在系統不支持內核搶占時,自旋鎖的實現也是空的,因為單核只有一個線程在執行,不會有內核搶占,從而資源也不會被其他線程訪問到。
- 其次,支持內核搶占,由於自旋鎖是禁止搶占內核的,所以不會有其他的進程因為等待鎖而自旋.
- 最后,只有在多cpu下,其他的cpu因為等待該cpu釋放鎖,而處於自旋狀態,不停輪詢鎖的狀態。所以這樣的話,如果一旦自旋鎖內代碼執行時間較長,等待該鎖的cpu會耗費大量資源,也是不同於信號量和互斥鎖的地方。
簡單來說,自旋鎖在內核中主要用來防止多處理器中並發訪問臨界區,防止內核搶占造成的競爭。
自旋鎖內睡眠禁止睡眠問題:如果自旋鎖鎖住以后進入睡眠,而此時又不能進行處理器搶占(鎖住會disable prempt),其他進程無法獲得cpu,這樣也不能喚醒睡眠的自旋鎖,因此不相應任何操作。
自旋鎖為什么廣泛用於內核:自旋鎖是一種輕量級的互斥鎖,可以更高效的對互斥資源進行保護。自旋鎖本來就只是一個很簡單的同步機制,在SMP之前根本就沒這個東西,一切都是Event之類的同步機制,這類同步機制都有一個共性就是:一旦資源被占用都會產生任務切換,任務切換涉及很多東西的(保存原來的上下文,按調度算法選擇新的任務,恢復新任務的上下文,還有就是要修改cr3寄存器會導致cache失效)這些都是需要大量時間的,因此用Event之類來同步一旦涉及到阻塞代價是十分昂貴的,而自旋鎖的效率就遠高於互斥鎖。
在Linux內核中何時使用spin_lock,何時使用spin_lock_irqsave很容易混淆。首先看一下代碼是如何實現的。
- spin_lock
spin_lock -----> raw_spin_lock
1 static inline void __raw_spin_lock(raw_spinlock_t *lock) 2 { 3 preempt_disable(); 4 spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); 5 LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); 6 }
1. 只禁止內核搶占,不會關閉本地中斷
2. 為何需要關閉內核搶占:假如進程A獲得spin_lock->進程B搶占進程A->進程B嘗試獲取spin_lock->由於進程B優先級比進程A高,先於A運行,而進程B又需要A unlock才得以運行,這樣死鎖。所以這里需要關閉搶占。 這個原理RTOS的mutex/semaphore是否相同?
a. 因為ThreadX的semaphore,假如進程B獲取sema失敗,會一直等待,直到A進程釋放,不會死鎖。
b. Mutex: mutex獲取一旦失敗,進程會進入sleep,直到其他進程釋放;而spin_lock則不同,會一直輪訓訪問,且直到時間片耗完。
- spin_lock_irq
spin_lock_irq------> raw_spin_lock_irq
1 static inline void __raw_spin_lock_irq(raw_spinlock_t *lock) 2 { 3 local_irq_disable(); 4 preempt_disable(); 5 spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); 6 LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); 7 }
1. 禁止內核搶占,且關閉本地中斷
2. 那么在spin_lock中關閉了內核搶占,不關閉中斷會出現什么情況呢?假如中斷中也想獲得這個鎖,會出現和spin_lock中舉得例子相同。所以這個時候,在進程A獲取lock之后,使用spin_lock_irq將中斷禁止,就不會出現死鎖的情況
3. 在任何情況下使用spin_lock_irq都是安全的。因為它既禁止本地中斷,又禁止內核搶占。
4. spin_lock比spin_lock_irq速度快,但是它並不是任何情況下都是安全的。
- spin_lock_irqsave
spin_lock_irqsave------>__raw_spin_lock_irqsave
1 static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock) 2 { 3 unsigned long flags; 4 5 local_irq_save(flags); 6 preempt_disable(); 7 spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); 8 /* 9 * On lockdep we dont want the hand-coded irq-enable of 10 * do_raw_spin_lock_flags() code, because lockdep assumes 11 * that interrupts are not re-enabled during lock-acquire: 12 */ 13 #ifdef CONFIG_LOCKDEP 14 LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); 15 #else 16 do_raw_spin_lock_flags(lock, &flags); 17 #endif 18 return flags; 19 }
1. 禁止內核搶占,關閉中斷,保存中斷狀態寄存器的標志位
2. spin_lock_irqsave在鎖返回時,之前開的中斷,之后也是開的;之前關,之后也是關。但是spin_lock_irq則不管之前的開還是關,返回時都是開的(?)。
3. spin_lock_irq在自旋的時候,不會保存當前的中斷標志寄存器,只會在自旋結束后,將之前的中斷打開。
1. spin_lock/spin_unlock:
進程A中調用了spin_lock(&lock)然后進入臨界區,此時來了一個中斷(interrupt),該中斷也運行在和進程A相同的CPU上,並且在該中斷處理程序中恰巧也會spin_lock(&lock), 試圖獲取同一個鎖。由於是在同一個CPU上被中斷,進程A會被設置為TASK_INTERRUPT狀態,中斷處理程序無法獲得鎖,會不停的忙等,由於進程A被設置為中斷狀態,schedule()進程調度就無法再調度進程A運行,這樣就導致了死鎖!
但是如果該中斷處理程序運行在不同的CPU上就不會觸發死鎖。 因為在不同的CPU上出現中斷不會導致進程A的狀態被設為TASK_INTERRUPT,只是換出。當中斷處理程序忙等被換出后,進程A還是有機會獲得CPU,執行並退出臨界區。所以在使用spin_lock時要明確知道該鎖不會在中斷處理程序中使用。
2. spin_lock_irq/spin_unlock_irq
spin_lock_irq----->raw_spin_lock_irq
spin_lock_irq 和 spin_unlock_irq, 如果你確定在獲取鎖之前本地中斷是開啟的,那么就不需要保存中斷狀態,解鎖的時候直接將本地中斷啟用就可以啦
3. spin_lock_irqsave/spin_unlock_irqrestore
使用spin_lock_irqsave在於你不期望在離開臨界區后,改變中斷的開啟/關閉狀態!進入臨界區是關閉的,離開后它同樣應該是關閉的!
如果自旋鎖在中斷處理函數中被用到,那么在獲取該鎖之前需要關閉本地中斷,spin_lock_irqsave 只是下列動作的一個便利接口:
1 保存本地中斷狀態(這里的本地即當前的cpu的所有中斷)
2 關閉本地中斷
3 獲取自旋鎖
解鎖時通過 spin_unlock_irqrestore完成釋放鎖、恢復本地中斷到之前的狀態等工作
參考微博:
1. 什么情況下使用什么樣的自旋鎖:http://blog.csdn.net/wesleyluo/article/details/8807919
2. 本地中斷概念的理解:http://blog.csdn.net/adaptiver/article/details/6177646
3. 深入理解自旋鎖:http://blog.csdn.net/vividonly/article/details/6594195
4. 自旋鎖和互斥量區別:http://blog.csdn.net/kyokowl/article/details/6294341
5. 自旋鎖的臨界區本地cpu不會發生任何進程調度:http://blog.chinaunix.net/uid-23769728-id-3367773.html
總結:
1. 這樣是否可以這么說,spin_lock為了防止內核的搶占死鎖,spin_lock_irq為了防止內核和中斷的搶占死鎖,spin_lock_irqsave為了防止進入自旋狀態丟掉之前的中斷狀態。
2.