1. 程式人生 > >深入分析_linux_spinlock_實現機制【轉】

深入分析_linux_spinlock_實現機制【轉】

源碼 idt 內存 獲取 編寫 存在 www 浪費 理論

轉自:http://blog.csdn.net/electrombile/article/details/51289813

在 x86 平臺上,spinlock 主要通過處理器的 lock 指令前綴實現當某個線程的一條指令訪問某個內存的時候,其他的線程的指令無法訪問該內存的功能。(可見都是由處理器特性來保證!)

因此在 spinlock 初始化階段,將鎖變量中的值某個值 k 賦為1。在加鎖的時候,使用 lock decl (%eax) 指令互斥地將該變量變成0,並且將結果是否問0 賦值給 EFLAGS寄存器 的對應位。只有加上鎖的線程才會結果才是0,其他線程的結果不是0。接著通過判斷該對應位判斷是否加上鎖。如果沒有加上,則循環執行 lock decl (%eax),直到加上為止。其中 %eax 是這個變量的地址。這裏用的是gcc 的AT&T語法的匯編。

再讀一下大神的文章

http://blog.chinaunix.net/uid-20543672-id-3252604.html

前言:

在復習休眠的過程中,我想驗證自旋鎖中不可休眠,所以編寫了一個在自旋鎖中休眠的模塊。但是在我的ARMv7的單核CPUTIA8芯片)中測試的時候,不會鎖死,並且自旋鎖可以多次獲取。實驗現象和我對自旋鎖和休眠的理解有出路。

我後來我將這個模塊放到自己的PC上測試,成功鎖死了,說明我的模塊原理上沒有問題。但是為什麽在ARM上會這樣呢???後來我將模塊給了我的兩個同事測試,在Omap3530中一樣不會鎖死,但是在S3C6410中成功的鎖死了。這是怎麽回事??我覺得應該是內核配置的問題,便讓同事將他的6410的內核配置給我對比一下,發現對於配置上的不同:6410spinlock上不過就是多了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系統不存在物理上的並行,所以可以閹割掉自旋的部分,剩下搶占和中斷操作部分即可。


  1. 到這裏其實就可以解釋為什麽我開始的實驗現象和預想的完全不同了:
  2. 由於UP系統(在不配置CONFIG_DEBUG_SPINLOCK的情況下),根本就沒有自旋鎖控制的部分,
  3. 多次獲得自旋鎖是可能的(這種編程本來就是錯誤的,只是我想看錯誤的現象而已)。


  1. 對於其中的一點疑惑:
  2. 1、在有禁用中斷的版本中,既然已經禁用了中斷,在本處理器上就不會被打斷,禁用搶占是否多余?
  3. (1)禁用了中斷可以避免因為中斷引起的搶占調度,但是如果在自旋鎖保護的臨界區中存在 preempt_disable();和 preempt_enable();對。
  4. 這樣在preempt_enable();就會引發搶占調度。
  5. (2)避免SMP系統中別的處理器執行調度程序使得本處理器的進程會被調度出去。?????
  6. 對於這個問題我不是很確定,還有深入研究調度系統後才會有準確的答案。
  • 五、自旋鎖變體的使用規則

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


  1. 不會在任何中斷例程中操作臨界區:

  2. static inline void spin_lock(spinlock_t *lock)

  3. static inline void spin_unlock(spinlock_t *lock)

  4. 如果在軟件中斷中操作臨界區:

  5. static inline void spin_lock_bh(spinlock_t *lock)

  6. static inline void spin_unlock_bh(spinlock_t *lock)

  7. bh代表bottom half,也就是中斷中的底半部,因內核中斷的底半部一般通過軟件中斷(tasklet等)來處理而得名。

  8. 如果在硬件中斷中操作臨界區:

  9. static inline void spin_lock_irq(spinlock_t *lock)

  10. static inline void spin_unlock_irq(spinlock_t *lock)

  11. 如果在控制硬件中斷的時候需要同時保存中斷狀態:

  12. spin_lock_irqsave(lock, flags)

  13. static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)

這些情況描訴似乎有點簡單,我在網上找到了一篇使用規則((轉)自旋鎖(spinlock) 解釋得經典,透徹),非常詳細。我稍作修改,轉載如下:

  1.   獲得自旋鎖和釋放自旋鎖有好幾個版本,因此讓讀者知道在什麽樣的情況下使用什麽版本的獲得和釋放鎖的宏是非常必要的。   如果被保護的共享資源只在進程上下文訪問和軟中斷(包括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上運行。
      如果被保護的共享資源在兩個或多個軟中斷上下文訪問,那麽這個共享資源當然更需要用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 和 STREX

spinlock與linux內核調度的關系

版權聲明:本文為博主原創文章,未經博主允許不得轉載。

深入分析_linux_spinlock_實現機制【轉】