訊號量與自旋鎖 |Linux,訊號量,自旋鎖,睡眠鎖,spinlock,semaphore 【轉帖】

來源:互聯網
上載者:User

為了避免並發,防止競爭。核心提供了一組同步方法來提供對共用資料的保護。 我們的重點不是介紹這些方法的詳細用法,而是強調為什麼使用這些方法和它們之間的差別。
Linux
使用的同步機制可以說從2.0到2.6以來不斷髮展完善。從最初的原子操作,到後來的訊號量,從大核心鎖到今天的自旋鎖。這些同步機制的發展伴隨
Linux從單一處理器到對稱式多處理器的過度;伴隨著從非搶佔核心到搶佔核心的過度。鎖機制越來越有效,也越來越複雜。
目前來說核心中原子操作多用來做計數使用,其它情況最常用的是兩種鎖以及它們的變種:一個是自旋鎖,另一個是訊號量。我們下面就來著重介紹一下這兩種鎖機制。

自旋鎖
------------------------------------------------------
    自旋鎖是專為防止多處理器並發而引入的一種鎖,它在核心中大量應用於中斷處理等部分(對於單一處理器來說,防止中斷處理中的並發可簡單採用關閉中斷的方式,不需要自旋鎖)。
自旋鎖最多隻能被一個核心任務持有,如果一個核心任務試圖請求一個已被爭用(已經被持有)的自旋鎖,那麼這個任務就會一直進行忙迴圈——旋轉——等待鎖重新可用。
要是鎖未被爭用,請求它的核心任務便能立刻得到它並且繼續進行。自旋鎖可以在任何時刻防止多於一個的核心任務同時進入臨界區,因此這種鎖可有效地避免多處理器上並發啟動並執行核心任務競爭共用資源。
事實上,自旋鎖的初衷就是:在短期間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖重新可用的期間進行自旋(特別浪費處理器時間),所以自旋鎖不應該被持有時間過長。如果需要長時間鎖定的話, 最好使用訊號量。
自旋鎖的基本形式如下:
spin_lock(&mr_lock);
//臨界區
spin_unlock(&mr_lock);

因為自旋鎖在同一時刻只能被最多一個核心任務持有,所以一個時刻只有一個線程允許存在於臨界區中。這點很好地滿足了對稱式多處理機器需要的鎖定服務。在單處 理器上,自旋鎖僅僅當作一個設定核心搶佔的開關。如果核心搶佔也不存在,那麼自旋鎖會在編譯時間被完全剔除出核心。
簡單的說,自旋鎖在核心中主要用來防止多處理器中並發訪問臨界區,防止核心搶佔造成的競爭。另外自旋鎖不允許任務睡眠(持有自旋鎖的任務睡眠會造成自死結——因為睡眠有可能造成持有鎖的核心任務被重新調度,而再次申請自己已持有的鎖),它能夠在中斷上下文中使用
死結:假設有一個或多個核心任務和一個或多個資源,每個核心都在等待其中的一個資源,但所有的資源都已經被佔用了。這便會發生所有核心任務都在相互等待,

但它們永遠不會釋放已經佔有的資源,於是任何核心任務都無法獲得所需要的資源,無法繼續運行,這便意味著死結發生了。自死瑣是說自己佔有了某個資源,然後
自己又申請自己已佔有的資源,顯然不可能再獲得該資源,因此就自縛手腳了。

訊號量
------------------------------------------------------
Linux中的訊號量是一種睡眠鎖。如果有一個任務試圖獲得一個已被持有的訊號量時,訊號量會將其推入等待隊列,然後讓其睡眠。這時處理器獲得自由去執行 其它代碼。當持有訊號量的進程將訊號量釋放後,在等待隊列中的一個任務將被喚醒,從而便可以獲得這個訊號量。
訊號量的睡眠特性,使得訊號量適用於鎖會被長時間持有的情況;只能在進程上下文中使用,因為中斷上下文中是不能被調度的;另外當代碼持有訊號量時,不可以再持有自旋鎖。

訊號量基本使用形式為:
static DECLARE_MUTEX(mr_sem);//聲明互斥訊號量
if(down_interruptible(&mr_sem))
//可被中斷的睡眠,當訊號來到,睡眠的任務被喚醒
//臨界區
up(&mr_sem);

訊號量和自旋鎖區別
------------------------------------------------------
雖然聽起來兩者之間的使用條件複雜,其實在實際使用中訊號量和自旋鎖並不易混淆。注意以下原則:
如果代碼需要睡眠——這往往是發生在和使用者空間同步時——使用訊號量是唯一的選擇。由於不受睡眠的限制,使用訊號量通常來說更加簡單一些。如果需要在自旋
鎖和訊號量中作選擇,應該取決於鎖被持有的時間長短。理想情況是所有的鎖都應該儘可能短的被持有,但是如果鎖的持有時間較長的話,使用訊號量是更好的選
擇。另外,訊號量不同於自旋鎖,它不會關閉核心搶佔,所以持有訊號量的代碼可以被搶佔。這意味者訊號量不會對影響調度反應時間帶來負面影響。

自旋鎖對訊號量
------------------------------------------------------
需求                     建議的加鎖方法

低開銷加鎖               優先使用自旋鎖
短期鎖定                 優先使用自旋鎖
長期加鎖                 優先使用訊號量
中斷上下文中加鎖          使用自旋鎖
持有鎖是需要睡眠、調度     使用訊號量

以下部分的來源:kcrazy的紙¨

自旋鎖我的理解就好比

    小A,小B,小C,小D 同住一個屋子,可屋子只有一間茅房和一個馬桶。他們誰想"便"的時候誰就要把茅房的門鎖上,然後佔據馬桶,比如小A正在佔有,聚精會神,非常愜意。碰巧小 C此時甚急,但沒辦法,因為小A已經把門上了鎖。於是小B在門口急得打轉,即為"自旋"。注意這個"自旋"兩個字用的好,小B不是一看門被上鎖就回屋睡覺 去了,而是在門口"自旋"。... 最終的結果是小A開鎖,小B佔用。而且在開鎖閉鎖過程中動作乾淨利落,不容他人搶在前面。

如此周而復始......

    這裡的 小A,B,C,D 即為處理器,茅房的鎖即為自旋鎖。當其他處理器想訪問這個公用的資源的時候就要先擷取這個鎖。如果鎖被佔用,則自旋(迴圈)等待。

小A的聚精會神代表了IRQL為2,開關鎖動作快表示為原子操作。

----------------------------------------------------------

不知道我理解的對還是不對,可能這樣舉例有些不恰當。有理解不對之處希望指點一二,以免誤入歧途,悔之晚矣。

----------------------------------------------------------

寫了個測試程式測試了一下:

KSPIN_LOCK spinlock;

NTSTATUS DriverEntry(
IN PDRIVER_OBJECT   DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS         Status;
UNICODE_STRING     DeviceName;
PDEVICE_OBJECT     DeviceObject;
HANDLE             ThreadHandle;
KIRQL             oldirql;
KIRQL             irql;
ULONG             Processor;
ULONG             i;

     DeviceObject = NULL;

     RtlInitUnicodeString( &DeviceName, deviceNameBuffer );

Status = IoCreateDevice( DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject );
if ( !NT_SUCCESS(Status) )
{
return Status;
}

     DriverObject->DriverUnload = DriverUnload;

     KeInitializeSpinLock( &spinlock );     // (2)

     PsCreateSystemThread( &ThreadHandle, THREAD_ALL_ACCESS, NULL, NULL, NULL, ThreadRoutine, NULL );

     i = 10000;

     KeAcquireSpinLock( &spinlock, &oldirql );

     while (i--)
{
__asm nop
irql = KeGetCurrentIrql();
Processor = KeGetCurrentProcessorNumber();
KdPrint(( "   [%d] CurrentIrql:\t%d", Processor, irql ));
}

     KeReleaseSpinLock( &spinlock, oldirql );

     return Status;
}

VOID
ThreadRoutine( IN PVOID StartContext )
{
KIRQL     oldirql;
KIRQL     irql;
ULONG     Processor;
ULONG     i;

     i = 10000;

     KeAcquireSpinLock( &spinlock, &oldirql );     // (1)

     while (i--)
{
__asm nop
irql = KeGetCurrentIrql();
Processor = KeGetCurrentProcessorNumber();

         KdPrint(( "**[%d] CurrentIrql:\t%d", Processor, irql ));
}

     KeReleaseSpinLock( &spinlock, oldirql );     // (1)
}

---------------------------------------------------------------------------------

首先說明一下我是雙核系統,如果是單核的話我想進入自旋鎖之後IRQL已經提高到 DPC 層級,第二個線程就跑不起來了。如果他神奇的跑了起來,那一定會發生死結。

分幾種情況測試:

1、就是上邊的代碼測試

   先抓到鎖的先跑,後抓到鎖的後跑。並且被鎖的期間的IRQL 為 DPC 層級。

2、去掉 標記 (1) 的兩行

   結果是兩個線程同時跑,一個占處理器 [0] 一個占處理器 [1]

   上鎖的那個 IRQL 層級是 DPC 級。

   沒上鎖的IRQL為 0

   即 自旋鎖 並不影響其他處理器的正常運行。除非其他處理器也想獲得這個鎖。

3、去掉 標記 (2) 的一行(spinlock 是全域變數)

   和 1 的結果相同,因為全域變數預設是初始化為0的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.