1. 程式人生 > >Linux裝置驅動中的併發控制之五(自旋鎖)

Linux裝置驅動中的併發控制之五(自旋鎖)

7.5 自旋鎖

7.5.1 自旋鎖的使用

自旋鎖(Spin Lock)是一種典型的對臨界資源進行互斥訪問的手段,名稱來源於它的工作方式。為了獲得一個自旋鎖,在某CPU上執行的程式碼需先執行一個原子操作,該操作測試並設定(Test-And-Set)某個記憶體變數。由於它是原子操作,所以在在該操作完成之前其他執行單元不可能訪問這個記憶體變數。如果測試結果表明鎖已經空閒,則程式獲得這個自旋鎖並繼續執行;如果測試結果表明鎖仍被佔用,程式將在一個小的迴圈內重複這個“測試並設定”操作,即進行所謂的“自旋”,通俗地說就是“在原地打轉”,如圖7.7所示。


當自旋鎖的持有者通過重置該變數釋放這個自旋鎖後,某個等待的“測試並設定”操作向其呼叫者報告鎖已釋放。

理解自旋鎖最簡單的方法是把自旋鎖作為一個變數看待,該變數把一個臨界區標記為“我當前在執行,請稍等一會”或者標記為“我當前不在執行,可以被使用”。如果A執行單元首先進入例程,它將持有自旋鎖;當B執行單元試圖進入同一個例程時,將獲知自旋鎖已被持有,需等到A執行單元釋放後才能進入。

Linux中與自旋鎖相關的操作主要有以下4種。

<linux/spinlock.h>

1.定義自旋鎖
spinlock_t lock;
2.初始化自旋鎖
spin_lock_init(lock)

該巨集用於動態初始化自旋鎖lock。

3.獲得自旋鎖
spin_lock(lock)

該巨集用於獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將在那裡自旋,直到該自旋鎖的持有者釋放。

spin_trylock(lock)
該巨集嘗試獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖並返回true,否則立即返回false,實際上不

再“在原地打轉”。

4.釋放自旋鎖
spin_unlock(lock)

該巨集釋放自旋鎖lock,它與spin_trylock或spin_lock配對使用。

自旋鎖一般這樣被使用:
/* 定義一個自旋鎖 */
spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; /* 獲取自旋鎖,保護臨界區 */
. . ./* 臨界區 */

spin_unlock (&lock) ; /* 解鎖 */

自旋鎖主要針對SMP(對稱多處理器)或單CPU但核心可搶佔的情況

,對於單CPU和核心不支援搶佔的系統,自旋鎖退化為空操作。在單CPU和核心可搶佔的系統中,自旋鎖持有期間禁止核心搶佔。由於核心可搶佔的單CPU系統的行為類似於SMP系統,因此,在這樣的單CPU系統中使用自旋鎖仍十分必要。在多核SMP的情況下,任何一個核拿到了自旋鎖,該核上的搶佔排程也暫時禁止了,但是沒有禁止另外一個核的搶佔排程。

儘管使用自旋鎖可以保證臨界區不受別的CPU和本CPU內的搶佔程序打擾,但是得到鎖的程式碼路徑在執行臨界區的時候,還可能受到中斷和底半部(BH)的影響。為了防止這種影響,需要用到自旋鎖的衍生。spin_lock()/spin_unlock()是自旋鎖機制的基礎,它們和關中斷local_irq_disable()/開中斷local_irq_enable()、關底半部local_bh_disable()/開底半部local_bh_enable()、關中斷並儲存狀態標誌local_irq_save()/開中斷並恢復狀態標誌local_irq_restore()結合形成了整套自旋鎖機制,關係如下:

spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_lock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()

spin_lock_irq()、spin_lock_irqsave()、spin_lock_bh()類似函式會為自旋鎖的使用繫好“安全帶”以避免突如其來的中斷駛入對系統造成的傷害。

在多核程式設計的時候,如果程序和中斷可能訪問同一片臨界資源,一般需要在程序上下文中呼叫

spin_lock_irqsave()/spin_unlock_irqrestore(),在中斷上下文中呼叫spin_lock()/spin_unlock(),如圖7.8所示。


這樣,在CPU0上,無論是程序上下文,還是中斷上下文獲得了自旋鎖,此後,如果CPU1無論是程序上下文,還是中斷上下文,想獲得同一自旋鎖,都必須忙等待,這避免一切核間併發的可能性。同時,由於每個核的程序上下文持有鎖的時候用的是spin_lock_irqsave(),所以該核上的中斷是不可能進入的,這避免了核內併發的可能性。

在使用自旋鎖中特別注意如下幾個問題。

1)自旋鎖實際上是忙等鎖,當鎖不可用時,CPU一直迴圈執行“測試並設定”該鎖直到可用而取得該鎖,CPU在等待自旋鎖時不做任何有用的工作,僅僅是等待。因此,只有在佔用鎖的時間極短的情況下,使用自旋鎖才是合理的。當臨界區很大,或有共享裝置的時候,需要較長時間佔用鎖,使用自旋鎖會降低系統的效能。

2)自旋鎖可能導致系統死鎖。引發這個問題最常見的情況是遞迴使用一個自旋鎖,即如果一個已經擁有某個自旋鎖的CPU想第二次獲得這個自旋鎖,則該CPU將死鎖。

3)在自旋鎖鎖定期間不能呼叫可能引起程序排程的函式。如果程序獲得自旋鎖之後再阻塞,如呼叫copy_from_user()、copy_to_user()、kmalloc()和msleep()等函式,則可能導致核心的崩潰。

4)在單核情況下程式設計時,也應該認為自己的CPU是多核的,驅動特別強調跨平臺的概念。比如,在單CPU的情況下,若中斷和程序可能訪問同一臨界區,程序裡呼叫spin_lock_irqsave()是安全的,在中斷裡其實不呼叫spin_lock()也沒有問題,因為spin_lock_irqsave()可以保證這個CPU的中斷服務程式不可能執行。但是,若CPU變成多核,spin_lock_irqsave()不能遮蔽另外一個核的中斷,所以另外一個核就可能造成併發問題。因此,無論如何,在中斷服務程式裡也應該呼叫spin_lock()。