Linux 網絡協議棧之內核鎖(五)—— 自旋鎖在搶佔(非搶佔)單核和多核中的作用

一、自旋鎖的實現

linux上的自旋鎖有三種實現:

1. 在單cpu,不可搶佔內核中,自旋鎖爲空操作。

2. 在單cpu,可搶佔內核中,自旋鎖實現爲“禁止內核搶佔”,並不實現“自旋”。

3. 在多cpu,可搶佔內核中,自旋鎖實現爲“禁止內核搶佔” + “自旋”。


關於搶佔式內核與非搶佔式內核:

a、非搶佔式內核

如果一個進程在內核態運行,其只有在以下兩種情況會被切換:

1.其運行完成(返回用戶空間)

2.主動讓出cpu(即主動調用schedule或內核中的任務阻塞——這同樣也會導致調用schedule)

b、搶佔式內核

如果一個進程在內核態運行,其只有在以下四種情況會被切換:

1.其運行完成(返回用戶空間)

2.主動讓出cpu(即主動調用schedule或內核中的任務阻塞——這同樣也會導致調用schedule)

3.當從中斷處理程序正在執行,且返回內核空間之前(此時可搶佔標誌premptcount須爲0) 。

4.當內核代碼再一次具有可搶佔性的時候,如解鎖及使能軟中斷等。


    禁止內核搶佔只是關閉“可搶佔標誌”,而不是禁止進程切換。顯式使用schedule或進程阻塞(此也會導致調用schedule)時,還是會發生進程調度的。


二、死鎖

1、第一種死鎖情況

     死鎖發生在多核的情況,下面來分析一下:

     首先,對於多核搶佔與多核非搶佔的情況,在使用自旋鎖時,其情況基本是一致的。因爲在多核搶佔的情況下,使用自旋鎖會禁止內核搶佔,這樣多核搶佔就相當於多核非搶佔的情況。

     那下面就只分析多核非搶佔的情況。

假設系統有A,B兩個CPU。

A上正在運行的a進程已獲得自旋鎖,並在臨界區運行。

B上正在運行的b進程企圖獲得自旋鎖,但由於自旋鎖已被佔用,於是b進程在B CPU上“自旋”空轉。

這時,如果在A上的a進程因程序阻塞,而被休眠。接着A會切換運行另一進程c。

若這個進程c也企圖獲取自旋鎖,c進程同樣會因爲鎖已被佔用,而在A上“自旋”空轉。

這時候,A上的a進程與c進程就形成了死鎖。a進程需要被c進程佔用的CPU,c進程需要被a進程佔用的鎖。

至於在單cpu內核上不會出現上述情況,因爲單cpu上的自旋鎖實際沒有“自旋功能”。


2、第二種死鎖情況

     想象你的內核代碼請求到一個自旋鎖並且在它的臨界區裏做它的事情,在中間某處,你的代碼失去了處理器。或許它已調用了一個函數(copy_from_user,假設)使進程進入眠。也或許,內核搶佔發威,一個更高優先級的進程將你的代碼推到了一邊(注意,這都是假設,自旋鎖其實不允許這些操作)。此時,正好某個別的線程想獲取同一個鎖,如果這個線程運行在和你的內核代碼不同的處理器上(幸運的情況),那麼它可能要自旋等待一段時間(可能很長),當你的代碼從休眠中喚醒或者重新得到處理器並釋放鎖,它就能得到鎖。而最壞的情況是,那個想獲取鎖得線程剛好和你的代碼運行在同一個處理器上,這時它將一直持有CPU進行自旋操作,而你的代碼是永遠不可能有任何機會來獲得CPU釋放這個鎖了,這就是悲催的死鎖。 


3、第三種死鎖情況

     spin_lock比spin_lock_irq速度快,但是它並不是任何情況下都是安全的。

舉個例子:

     進程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時要明確知道該鎖不會在中斷處理程序中使用。


三、自旋鎖特性

      自旋鎖有幾個重要的特性:

1、被自旋鎖保護的臨界區代碼執行時不能進入休眠。

2、被自旋鎖保護的臨界區代碼執行時是不能被被其他中斷中斷。

3、被自旋鎖保護的臨界區代碼執行時,內核不能被搶佔。

從這幾個特性可以歸納出一個共性:被自旋鎖保護的臨界區代碼執行時,它不能因爲任何原因放棄處理器。 

注:所以現代處理器在處理自旋鎖時都會設定自旋上限時間以防死鎖. 另自旋鎖在單核非搶佔式CPU上是無效的.被設爲空操作,不做任何事. 

     可能你會奇怪,持有自旋鎖的進程在執行內核代碼時是不能被搶佔的,那麼爲什麼在可搶佔式系統中有用呢.其實linux在設計可搶佔式系統的自旋鎖時只是把自旋鎖設計爲"只是禁止內核搶佔",而沒有自旋(所以使用自旋鎖的代碼一定要可以很快執行完,否則進程就一直持着鎖不釋放,也不可被搶佔).可是理解爲只是實現了一個不被打擾的原子操作,操作完後釋放鎖.所以它不能休眠. 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章