天天看點

關于核心中spinlock的一些個人了解 【轉】

在多CPU的環境下情況就比較複雜了,因為同時可能有幾個程式在運作(是真正的同時),是以必須要定義一個 變量當作鎖的功能,linux是這樣規定的,當這個變量為1時,那麼其保護的變量可以被通路,當其值為0時,那麼其保護的臨界資料不可以被通路,其中,要 改變變量鎖的值也很有學問,就是不能讓幾個CPU同時去改,負責就會出現不同步的情況。如spin_lock在多cpu的時候就被?/

在這裡,我主要把自己對核心中spinlock的一些了解寫出來,并不是要告訴大家什麼(因為我對我所說的也不能确定),而是希望大家對我的這些了解對的地方給我肯定,錯誤的地方給我指出。

和spinlock 相關的檔案主要有兩個,一個是include/linux/spinlock.h,主要是提供關于和硬體無關的spinlock的幾個對外主函數,一個是 include/asm-XXX/spinlock.h,用來提供和硬體相關的功能函數。另外,在2.6的核心中,又多了一個檔案, include/linux/preempt.h,為新增加的搶占式多任務功能提供一些服務。

spinlock的作用:spinlock系列函數主要用于保護臨界資料(非常重要的資料)不被同時通路(給臨界資料加鎖),用以達到多任務的同步。如果一個資料目前不可通路,那麼就一直等,直到可以通路為止。

spinlock 函數的使用前提:首先,spinklock函數隻能使用在核心中,或者說隻能使用在核心狀态下,在2.6以前的核心是不可搶占的,也就是說,當運作于核心狀态下時,是不容許切換到其他程序的。而在2.6以後的核心中,編譯核心的時候多了一個選項,可以配置核心是否可以被搶占,這也就是為什麼在2.6的核心中多了一個preempt.h的原因。

spinlock主要包含以下幾個函數:

spin_lock

spin_unlock

spin_lock_irqsave

spin_lock_irq

spin_unlock_irqrestore

spin_unlock_irq

另 外還有其他很多,如關于讀者寫者的一套函數,關于bottom half一套函數(關于bottom half的代碼我還沒有讀到),還有還提供了一套用bit實作加鎖的函數,由于大概意思都相同,是以我這裡就不說了(隻想簡單說說,沒想到東西還挺多,我 的手都快凍僵了,江南的冬天真的受不了:)

spinlock函數根據機器的配置分為兩套,單CPU和多CPU,先來看看單CPU的情況。

在 單CPU的情況下,spin_lock和spin_unlock函數都被定義成空操作(do { } while(0)),這是因為我們上面說的,核心不可以被搶占的原因。是以,在單CPU的情況下,隻要你能夠保證你要保護的臨界資料不會在中斷中用到的 話,那麼你的資料已經是受保護的了,不需要做任何操作。在2.6核心中,這兩個函數就不再這麼簡單了,因為核心也有可能被其他程式中斷,是以要保護資料, 還要讓排程程式暫時不排程此段程式,也就是說,暫時禁止搶占式任務排程功能,是以在上面兩個函數中分别多了一個

需要澄清的是,互斥手段的選擇,不是根據臨界區的大小,而是根據臨界區的性質,以及 

有哪些部分的代碼,即哪些核心執行路徑來争奪。 

從嚴格意義上說,semaphore和spinlock_XXX屬于不同層次的互斥手段,前者的 

實作有賴于後者,這有點象HTTP和TCP的關系,都是協定,但層次是不同的。 

先說semaphore,它是程序級的,用于多個程序之間對資源的互斥,雖然也是在 

核心中,但是該核心執行路徑是以程序的身份,代表程序來争奪資源的。如果 

競争不上,會有context switch,程序可以去sleep,但CPU不會停,會接着運作 

其他的執行路徑。從概念上說,這和單CPU或多CPU沒有直接的關系,隻是在 

semaphore本身的實作上,為了保證semaphore結構存取的原子性,在多CPU中需要 

spinlock來互斥。 

在核心中,更多的是要保持核心各個執行路徑之間的資料通路互斥,這是最基本的 

互斥問題,即保持資料修改的原子性。semaphore的實作,也要依賴這個。在單CPU 

中,主要是中斷和bottom_half的問題,是以,開關中斷就可以了。在多CPU中, 

又加上了其他CPU的幹擾,是以需要spinlock來幫助。這兩個部分結合起來, 

就形成了spinlock_XXX。它的特點是,一旦CPU進入了spinlock_XXX,它就不會 

幹别的,而是一直空轉,直到鎖定成功為止。是以,這就決定了被 

spinlock_XXX鎖住的臨界區不能停,更不能context switch,要存取完資料後趕快 

出來,以便其他的在空轉的執行路徑能夠獲得spinlock。這也是spinlock的原則 

所在。如果目前執行路徑一定要進行context switch,那就要在schedule()之前 

釋放spinlock,否則,容易死鎖。因為在中斷和bh中,沒有context,無法進行 

context switch,隻能空轉等待spinlock,你context switch走了,誰知道猴年 

馬月才能回來。 

因為spinlock的原意和目的就是保證資料修改的原子性,是以也沒有理由在spinlock 

鎖住的臨界區中停留。 

spinlock_XXX有很多形式,有 

<a></a>

那麼,在什麼情況下具體用哪個呢?這要看是在什麼核心執行路徑中,以及要與哪些核心 

執行路徑互相斥。我們知道,核心中的執行路徑主要有: 

這樣,考慮這四個方面的因素,通過判斷我們要互斥的資料會被這四個因素中 

的哪幾個來存取,就可以決定具體使用哪種形式的spinlock。如果隻要和其他CPU 

互斥,就要用spin_lock/spin_unlock,如果要和irq及其他CPU互斥,就要用 

spin_lock_irq/spin_unlock_irq,如果既要和irq及其他CPU互斥,又要儲存 

EFLAG的狀态,就要用spin_lock_irqsave/spin_unlock_irqrestore,如果 

要和bh及其他CPU互斥,就要用spin_lock_bh/spin_unlock_bh,如果不需要和 

其他CPU互斥,隻要和irq互斥,則用local_irq_disable/local_irq_enable, 

如果不需要和其他CPU互斥,隻要和bh互斥,則用local_bh_disable/local_bh_enable, 

等等。值得指出的是,對同一個資料的互斥,在不同的核心執行路徑中, 

所用的形式有可能不同(見下面的例子)。 

舉一個例子。在中斷部分中有一個irq_desc_t類型的結構數組變量irq_desc[], 

該數組每個成員對應一個irq的描述結構,裡面有該irq的響應函數等。 

在irq_desc_t結構中有一個spinlock,用來保證存取(修改)的互斥。 

對于具體一個irq成員,irq_desc[irq],對其存取的核心執行路徑有兩個,一是 

在設定該irq的響應函數時(setup_irq),這通常發生在module的初始化階段,或 

系統的初始化階段;二是在中斷響應函數中(do_IRQ)。代碼如下: 

在setup_irq()中,因為其他CPU可能同時在運作setup_irq(),或者在運作setup_irq()時, 

本地irq中斷來了,要執行do_IRQ()以修改desc-&gt;status。為了同時防止來自其他CPU和 

本地irq中斷的幹擾,如[1][2][3]處所示,使用了spin_lock_irqsave/spin_unlock_irqrestore() 

而在do_IRQ()中,因為do_IRQ()本身是在中斷中,而且此時還沒有開中斷,本CPU中沒有 

什麼可以中斷其運作,其他CPU則有可能在運作setup_irq(),或者也在中斷中,但這二者 

對本地do_IRQ()的影響沒有差別,都是來自其他CPU的幹擾,是以隻需要用spin_lock/spin_unlock, 

如[4][5][6][7]處所示。值得注意的是[5]處,先釋放該spinlock,再調用具體的響應函數。 

再舉個例子: 

這裡,對tasklet_hi_vec[cpu]的修改,不存在CPU之間的競争,因為每個CPU有各自獨立的資料, 

是以隻要防止irq的幹擾,用local_irq_disable/local_irq_enable即可,如[8][9][10][11]處 

所示。  

【新浪微網誌】 張昺華--sky

【twitter】 @sky2030_

【facebook】 張昺華 zhangbinghua

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀