1. 程式人生 > >linux核心 訊號量與自旋鎖、延時函式比較

linux核心 訊號量與自旋鎖、延時函式比較

       在驅動程式中,當多個執行緒同時訪問相同的資源時(驅動程式中的全域性變數是一種典型的共享資源),可能會引發"競態",因此我們必須對共享資源進行併發控制。Linux核心中解決併發控制的最常用方法是自旋鎖與訊號量(絕大多數時候作為互斥鎖使用)。

  自旋鎖與訊號量"類似而不類",類似說的是它們功能上的相似性,"不類"指代它們在本質和實現機理上完全不一樣,不屬於一類。

  自旋鎖不會引起呼叫者睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈檢視是否該自旋鎖的保持者已經釋放了鎖,"自旋"就是"在原地打轉"而訊號量則引起呼叫者睡眠,它把程序從執行佇列上拖出去,除非獲得鎖。這就是它們的

"不類"。

  但是,無論是訊號量,還是自旋鎖,在任何時刻,最多隻能有一個保持者,即在任何時刻最多隻能有一個執行單元獲得鎖。這就是它們的"類似"。

  鑑於自旋鎖與訊號量的上述特點,一般而言,自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用;訊號量適合於保持時間較長的情況,會只能在程序上下文使用。如果被保護的共享資源只在程序上下文訪問,則可以以訊號量來保護該共享資源,如果對共享資源的訪問時間非常短,自旋鎖也是好的選擇。但是,如 果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理控制代碼和頂半部即軟中斷),就必須使用自旋鎖。

區別總結如下:
   1、由於爭用訊號量的程序在等待鎖重新變為可用時會睡眠,所以訊號量適用於鎖會被長時間持有的情況。
   2、相反,鎖被短時間持有時,使用訊號量就不太適宜了,因為睡眠引起的耗時可能比鎖被佔用的全部時間還要長。
   3、由於執行執行緒在鎖被爭用時會睡眠,所以只能在程序上下文中才能獲取訊號量鎖,因為在中斷上下文中(使用自旋鎖)是不能進行排程的。
   4、你可以在持有訊號量時去睡眠(當然你也可能並不需要睡眠),因為當其它程序試圖獲得同一訊號量時不會因此而死鎖,(因為該程序也只是去睡眠而已,而你最終會繼續執行的)。
   5、在你佔用訊號量的同時不能佔用自旋鎖,因為在你等待訊號量時可能會睡眠,而在持有自旋鎖時是不允許睡眠的。
   6、訊號量鎖保護的臨界區可包含可能引起阻塞的程式碼,而自旋鎖則絕對要避免用來保護包含這樣程式碼的臨界區,因為阻塞意味著要進行程序的切換,如果程序被切換出去後,另一程序企圖獲取本自旋鎖,死鎖就會發生。
   7、訊號量不同於自旋鎖,它不會禁止核心搶佔(自旋鎖被持有時,核心不能被搶佔),所以持有訊號量的程式碼可以被搶佔,這意味著訊號量不會對排程的等待時間帶來負面影響。
  除了以上介紹的同步機制方法以外,還有BKL(大核心鎖),Seq鎖等。
  BKL是一個全域性自旋鎖,使用它主要是為了方便實現從Linux最初的SMP過度到細粒度加鎖機制。
  Seq鎖用於讀寫共享資料,實現這樣鎖只要依靠一個序列計數器。


 

sem就是一個睡眠鎖.如果有一個任務試圖獲得一個已被持有的訊號量時,訊號量會將其推入等待佇列,然後讓其睡眠。這時處理器獲得自由去執行其它程式碼。當持有訊號量的程序將訊號量釋放後,在等待佇列中的一個任務將被喚醒,從而便可以獲得這個訊號量。訊號量一般在用程序上下文中.它是為了防止多程序同時訪問一個共享資源(臨界區).

spin_lock叫自旋鎖.就是當試圖請求一個已經被持有的自旋鎖.這個任務就會一直進行 忙迴圈——旋轉——等待,直到鎖重新可用(它會一直這樣,不釋放CPU,它只能用在短時間加鎖).它是為了防止多個CPU同時訪問一個共享資源(臨界區).它一般用在中斷上下文中,因為中斷上下文不能被中斷,也不能被排程.

 

自旋鎖對訊號量

 

需求              建議的加鎖方法

低開銷加鎖           優先使用自旋鎖

短期鎖定            優先使用自旋鎖

長期加鎖            優先使用訊號量

中斷上下文中加鎖        使用自旋鎖

持有鎖是需要睡眠、排程     使用訊號量 

 

程序間的sem.執行緒間的sem與核心中的sem的功能就很類似.

程序間的sem,執行緒間的sem功能是一樣的.只是執行緒的sem,它在同一個程序空間,他的初始化,使用更方便.

程序間的sem,就是程序間通訊的一部分,使用semget,semop等系統呼叫來完成.

核心中的sem 被鎖定,就等於被呼叫的程序佔有了這個sem.其它程序就只能進行睡眠佇列.這與程序間的sem基本一致.

 

 

區別

Spin_lock

semaphore

保護的物件

一段程式碼

一個裝置(必要性不強),

一個變數,

一段程式碼

保護區可被搶佔

不可以(會被中斷打斷)

可以。

可允許在保護物件(程式碼)中休眠

不可以

可以。但最好不這樣。

保護區能否被中斷打斷

可以,這樣容易引發死鎖。

最好是關了中斷再使用此鎖。

因為有可能中斷處理例程也需要得到同一個鎖。

可以。

其它功能

 

可完成同步,有傳達資訊的能力。

     

試圖佔用鎖不成功後,程序的表現

不放開CPU,自己自旋。

進入一個等待佇列。

釋放鎖後,還有其它程序等待時,核心如何處理

哪個程序得到執行的權力,它就得到了鎖。

從等待佇列中選一個出來佔用此sem.

核心對使用者的要求

被保護的程式碼執行時間要短,是原子的,

不能主動的休眠。

不能呼叫有可以休眠的核心函式。

 

風險

發生死鎖

 
 

不允許鎖的持有者二次請求同一個鎖。

不允許鎖的持有者二次請求同一個鎖。

訊號量在生產者與消費者模式中可以進行同步。

當sem的down和UP分別出現在對立函式中(讀,寫函式),其實這就是在傳達一種資訊。表示當前是否有資料可讀的資訊。

read_somthing()

{

     down(裝置)   佔用了此裝置          此時沒有其它人都使用此裝置上的所有操作(函式)

     if(有資料)

    {

       讀完它。

          ()

    }

    else

     {

              up(裝置)

              down(有資料的sem)sem=1表示有資料,為0表示無資料。

      }

}

write_somthing()

{

     down(裝置)   佔用了此裝置          此時沒有其它人都使用此裝置上的所有操作(函式)

     if(有資料)

    {

          不寫。

          up(裝置)

          return

    }

    else

     {

              寫入資料

              up(有資料的sem)sem=1表示有資料,為0表示無資料。

             up(裝置)

            return;

      }

}

 

總結:

訊號量適用於長時間片段,可能會睡眠(掛起排程)  所以只能用在程序上下文,不能用在中斷上下文。
自旋鎖使用於短時間片段  不會睡眠(掛起排程)抱著cpu不放,  用在中斷上下文  但是必須先關閉本地中斷,否則很可能因為
獲取不到自旋鎖又抱著cpu不讓別人持有而釋放自旋鎖從而陷入死鎖。

訊號量可以用的前提下儘量用訊號量,萬不得已(中斷中)使用自旋鎖,短時間片段比較適合自旋鎖,排程(程序之間的切換)本身
佔用時間,自旋等待的時間很短時就沒必要排程了,這時選用自旋鎖死抱cpu不放比較好。


時間函式

忙等待(一直消耗cpu)

ndelay、udelay、mdelay

unsigned long delay = jiffies + 100;

while(time_before(jiffies,delay));

 

睡著等待(不會一直消耗cpu)

msleep()、msleep_interruptible()、ssleep()、interruptible_sleep_on_timeout();