深入分析Linux自旋鎖

來源:互聯網
上載者:User

 前言:     在複習休眠的過程中,我想驗證自旋鎖中不可休眠,所以編寫了一個在自旋鎖中休眠的模組。但是在我的ARMv7的單核CPU(TI的A8晶片)中測試的時候,不會鎖死,並且自旋鎖可以多次擷取。實驗現象和我對自旋鎖和休眠的理解有出路。

      我後來我將這個模組放到自己的PC上測試,成功鎖死了,說明我的模組原理上沒有問題。但是為什麼在ARM上會這樣呢???後來我將模組給了我的兩個同事測試,在Omap3530中一樣不會鎖死,但是在S3C6410中成功的鎖死了。這是怎麼回事??

      我覺得應該是核心配置的問題,便讓同事將他的6410的核心配置給我對比一下,發現對於配置上的不同:6410在spinlock上不過就是多了CONFIG_DEBUG_SPINLOCK的自旋鎖調試功能。於是我將自己板子的核心也加了這個配置,並讓同事Omap3530的核心也加了這個配置進行測試,結果正常了:鎖死!!一個調試選項怎麼會影響到自旋鎖的準系統?這說明我對自旋鎖的理解不正確。這種時候RTFSC就是最好的解決辦法。

     我通過閱讀核心的自旋鎖源碼發現:如果核心配置為SMP系統,自旋鎖就按SMP系統上的要求來實現真正的自旋等待,但是對於UP系統,自旋鎖僅做搶佔和中斷操作,沒有實現真正的“自旋”。如果配置了CONFIG_DEBUG_SPINLOCK,那麼自旋鎖按照SMP系統來編譯。

      但是為什麼在UP系統中不需要真正的“帶有自旋的”自旋鎖呢?其實在理解了自旋鎖的概念和由來,這個問題就迎刃而解了。所以我重新尋找了關於自旋鎖的資料,認真研究了自旋鎖的實現和相關內容。

  • 自旋鎖spinlock的由來

    眾所周知,自旋鎖最初就是為了SMP系統設計的,實現在多處理器情況下保護臨界區。所以在SMP系統中,自旋鎖的實現是完整的本來面目。但是對於UP系統,自旋鎖可以說是SMP版本的閹割版。因為只有在SMP系統中的自旋鎖才需要真正“自旋”。

  • 自旋鎖的目的

     自旋鎖的實現是為了保護一段短小的臨界區作業碼,保證這個臨界區的操作是原子的,從而避免並發的競爭冒險。在Linux核心中,自旋鎖通常用於包含核心資料結構的操作,你可以看到在許多核心資料結構中都嵌入有spinlock,這些大部分就是用於保證它自身被操作的原子性,在操作這樣的結構體時都經曆這樣的過程:上鎖-操作-解鎖。

      如果核心控制路徑發現自旋鎖“開著”(可以擷取),就擷取鎖並繼續自己的執行。相反,如果核心控制路徑發現鎖由運行在另一個CPU上的核心控制路徑“鎖著”,就在原地“旋轉”,反覆執行一條緊湊的迴圈檢測指令,直到鎖被釋放。 自旋鎖是迴圈檢測“忙等”,即等待時核心無事可做(除了浪費時間),進程在CPU上保持運行,所以它保護的臨界區必須小,且操作過程必須短。不過,自旋鎖通常非常方便,因為很多核心資源只鎖1毫秒的時間片段,所以等待自旋鎖的釋放不會消耗太多CPU的時間。

  • 自旋鎖需要做的工作

      從保證臨界區訪問原子性的目的來考慮,自旋鎖應該阻止在代碼運行過程中出現的任何並發乾擾。這些“幹擾”包括:

       1、中斷,包括硬體中斷和軟體中斷 (僅在中斷代碼可能訪問臨界區時需要)

         這種幹擾存在於任何系統中,一個中斷的到來導致了中斷常式的執行,如果在中斷常式中訪問了臨界區,原子性就被打破了。所以如果在某種中斷常式中存在訪問某個臨界區的代碼,那麼就必須用spinlock保護。對於不同的中斷類型(硬體中斷和軟體中斷)對應於不同版本的自旋鎖實現,其中包含了中斷禁用和開啟的代碼。但是如果你保證沒有中斷代碼會訪問臨界區,那麼使用不帶中斷禁用的自旋鎖API即可。 

      2、核心搶佔(僅存在於可搶佔核心中)

         在2.6以後的核心中,支援核心搶佔,並且是可配置的。這使UP系統和SMP類似,會出現核心態下的並發。這種情況下進入臨界區就需要避免因搶佔造成的並發,所以解決的方法就是在加鎖時禁用搶佔(preempt_disable(); ),在開鎖時開啟搶佔(preempt_enable();注意此時會執行一次搶佔調度)。 

     3、其他處理器對同一臨界區的訪問 (僅SMP系統) 

        在SMP系統中,多個物理處理器同時工作,導致可能有多個進程物理上的並發。這樣就需要在記憶體加一個標誌,每個需要進入臨界區的代碼都必須檢查這個標誌,看是否有進程已經在這個臨界區中。這種情況下檢查標誌的代碼也必須保證原子和快速,這就要求必須精細地實現,正常情況下每個構架都有自己的彙編實現方案,保證檢查的原子性。

      

有些人會以為自旋鎖的自旋檢測可以用for實現,這種想法“Too young, too simple, sometimes naive”!你可以在理論上用C去解釋,但是如果用for,起碼會有如下兩個問題:

(1)你如何保證在SMP下其他處理器不會同時訪問同一個的標誌呢?(也就是標誌的獨佔訪問)

(2)必須保證每個處理器都不會去讀取快取而是真正的記憶體中的標誌(可以實現,編程上可以用volitale)

要根本解決這個問題,需要在晶片底層實現物理上的記憶體位址獨佔訪問,並且在實現上使用特殊的彙編指令訪問。請看參考資料中對於自旋鎖的實現分析。以arm為例,從存在SMP的ARM構架指令集開始(V6、V7),採用LDREX和STREX指令實現真正的自旋等待。

  • 自旋鎖操作組成 
     根據上的介紹,我們很容易知道自旋鎖的組成:

中斷控制(僅在中斷代碼可能訪問臨界區時需要)搶佔控制(僅存在於可搶佔核心中需要)自旋鎖標誌控制  (僅SMP系統需要)中斷控制是按代碼訪問臨界區的不同而在編程時選用不同的變體,有些API中有,有些沒有。而搶佔控制和自旋鎖標誌控制依據核心配置(是否支援核心搶佔)和硬體平台(是否為SMP)的不同而在編譯時間確定。如果不需要,相應的控制碼就編譯為空白函數。 

對於非搶佔式核心,由自旋鎖所保護的每個臨界區都有禁止核心搶佔的API,但是為空白操作。

由於UP系統不存在物理上的並行,所以可以閹割掉自旋的部分,剩下搶佔和中斷操作部分即可。  

 到這裡其實就可以解釋為什麼我開始的實驗現象和預想的完全不同了: 由於UP系統(在不配置CONFIG_DEBUG_SPINLOCK的情況下),根本就沒有自旋鎖控制的部分,多次獲得自旋鎖是可能的(這種編程本來就是錯誤的,只是我想看錯誤的現象而已)。對於其中的一點疑惑: 在有禁用中斷的版本中,既然已經禁用了中斷,在本處理器上就不會被打斷,禁用搶佔是否多餘?      1、禁用了中斷可以避免因為中斷引起的搶佔調度,但是如果在自旋鎖保護的臨界區中存在 preempt_disable()               和 preempt_enable()對。這樣在preempt_enable();就會引發搶佔調度。
     2、避免SMP系統中別的處理器執行發送器使得本處理器的進程會被調度出去。?????               對於這個問題我不是很確定,還有深入研究調度系統後才會有準確的答案。

  • 自旋鎖變體的使用規則

        不論是搶佔式UP、非搶佔式UP還是SMP系統,只要在某類中斷代碼可能訪問臨界區,就需要控制中斷,保證操作的原子性。所以這個和模組代碼中臨界區的訪問還有關係,是否可能在中斷中操作臨界區,只有程式員才知道。所以自旋鎖API中有針對不同中斷類型的自旋鎖變體:

不會在任何中斷常式中操作臨界區:

static inline void spin_lock(spinlock_t *lock)
static inline void spin_unlock(spinlock_t *lock)

如果在軟體中斷中操作臨界區: 
static inline void spin_lock_bh(spinlock_t *lock)

static inline void spin_unlock_bh(spinlock_t *lock)
bh代表bottom half,也就是中斷中的底半部,因核心中斷的底半部一般通過軟體中斷(tasklet等)來處理而得名。

如果在硬體中斷中操作臨界區:
static inline void spin_lock_irq(spinlock_t *lock)
static inline void spin_unlock_irq(spinlock_t *lock)

如果在控制硬體中斷的時候需要同時儲存中斷狀態:
spin_lock_irqsave(lock, flags)
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)

 

這些情況描訴似乎有點簡單,我在網上找到了一篇使用規則,非常詳細。我稍作修改,轉載如下:

獲得自旋鎖和釋放自旋鎖有好幾個版本,因此讓讀者知道在什麼樣的情況下使用什麼版本的獲得和釋放鎖的宏是非常必要的。

如果被保護的共用資源只在進程上下文訪問和非強制中斷(包括tasklet、timer)上下文訪問,那麼當在進程上下文訪問共用資源時,可能被非強制中斷打斷,從而可能進入非強制中斷上下文來對被保護的共用資源訪問,因此對於這種情況,對共用資源的訪問必須使用spin_lock_bh和spin_unlock_bh來保護。當然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它們失效了本地硬中斷,失效硬中斷隱式地也失效了非強制中斷。但是使用spin_lock_bh和spin_unlock_bh是最恰當的,它比其他兩個快。

如果被保護的共用資源只在兩個或多個tasklet或timer上下文訪問,那麼對共用資源的訪問僅需要用spin_lock和spin_unlock來保護,不必使用_bh版本,因為當tasklet或timer運行時,不可能有其他tasklet或timer在當前CPU上運行。

如果被保護的共用資源只在一個tasklet或timer上下文訪問,那麼不需要任何自旋鎖保護,因為同一個tasklet或timer只能在一個CPU上運行,即使是在SMP環境下也是如此。實際上tasklet在調用tasklet_schedule標記其需要被調度時已經把該tasklet綁定到當前CPU,因此同一個tasklet決不可能同時在其他CPU上運行。timer也是在其被使用add_timer添加到timer隊列中時已經被幫定到當前CPU,所以同一個timer絕不可能運行在其他CPU上。當然同一個tasklet有兩個執行個體同時運行在同一個CPU就更不可能了。

如果被保護的共用資源只在一個非強制中斷(tasklet和timer除外)上下文訪問,那麼這個共用資源需要用spin_lock和spin_unlock來保護,因為同樣的非強制中斷可以同時在不同的CPU上運行。

如果被保護的共用資源在非強制中斷(包括tasklet和timer)或進程上下文和硬中斷上下文訪問,那麼在非強制中斷或進程上下文訪問期間,可能被硬中斷打斷,從而進入硬中斷上下文對共用資源進行訪問,因此,在進程或非強制中斷上下文需要使用spin_lock_irq和spin_unlock_irq來保護對共用資源的訪問。

而在中斷處理控制代碼中使用什麼版本,需依情況而定,如果只有一個中斷處理控制代碼訪問該共用資源,那麼在中斷處理控制代碼中僅需要spin_lock和spin_unlock來保護對共用資源的訪問就可以了。因為在執行中斷處理控制代碼期間,不可能被同一CPU上的非強制中斷或進程打斷。

但是如果有不同的中斷處理控制代碼訪問該共用資源,那麼需要在中斷處理控制代碼中使用spin_lock_irq和spin_unlock_irq來保護對共用資源的訪問。

在使用spin_lock_irq和spin_unlock_irq的情況下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具體應該使用哪一個也需要依情況而定,如果可以確信在對共用資源訪問前中斷是使能的,那麼使用spin_lock_irq更好一些。因為它比spin_lock_irqsave要快一些,但是如果你不能確定是否中斷使能,那麼使用spin_lock_irqsave和spin_unlock_irqrestore更好,因為它將恢複訪問共用資源前的中斷標誌而不是直接使能中斷。

當然,有些情況下需要在訪問共用資源時必須中斷失效,而訪問完後必須中斷使能,這樣的情形使用spin_lock_irq和spin_unlock_irq最好。

spin_lock用於阻止在不同CPU上的執行單元對共用資源的同時訪問以及不同進程上下文互相搶佔導致的對共用資源的非同步訪問,而中斷失效和非強制中斷失效卻是為了阻止在同一CPU上非強制中斷或中斷對共用資源的非同步訪問。

以上是我對自旋鎖的理解和使用上的總結,對與自旋鎖的實現,其實網上已經有之類文章了,我不廢話。由於自旋鎖涉及到核心搶佔,所有最好還是學習以下搶佔的相關知識。參考資料如下:

分析Linux中Spinlock在ARM及X86平台上的實現

ARM的SWP和LDREX STREX指令

4.2.12. LDREX 和 STREXspinlock與linux核心調度的關係
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.