內核與驅動_03_自旋鎖

幾個常見的鎖

  • 多線程中如鏈表等數據結構的同步問題是進行驅動開發時必須所考慮到的問題,這個時候就必須使用到鎖。

讀寫鎖

  • 特點:
    • 讀寫所有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態。
    • 其中,只有一個線程可以佔有寫狀態的鎖,但是可以有多個線程佔用讀狀態的鎖,這也是它可以實現高併發的原因。
    • 當其處於寫狀態鎖下,任何想要嘗試獲得鎖的線程都會被阻塞,直到寫狀態鎖被釋放;如果是處於讀狀態鎖下,允許其它線程獲得它的讀狀態鎖,但是不允許獲得它的寫狀態鎖,直到所有線程的讀狀態鎖被釋放 ;爲了避免想要嘗試寫操作的線程一致得不到寫狀態鎖,當處於讀模式的讀寫鎖收到一個試圖對其進行寫模式的加鎖操作時,便會阻塞後面所有向獲得讀狀態鎖的線程, 這樣當讀模式的鎖解鎖後,要獲得寫狀態鎖的線程能夠訪問此鎖保護的資源。
    • 讀寫鎖非常適合資源的讀操作遠遠多於寫操作的情況。

互斥鎖

  • 特點:
    • 在訪問共享資源之前進行加鎖操作,訪問資源完畢後進行解鎖操作。枷鎖後,任何其它試圖再次加鎖的線程都會被阻塞,直到當前佔用鎖的線程解鎖。
    • 如果解鎖時有一個以上的線程阻塞,那麼所有該鎖上的線程都被編程就緒狀態, 第一個變爲就緒狀態的線程又執行加鎖操作,那麼其他的線程又會進入等待。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源。

自選鎖

  • 自旋鎖(Spin lock)特點:

    • 和互斥鎖類似,不過其不會引起調用者睡眠,所以效率遠高於互斥鎖,如果自選鎖已經被別的執行單元保持,那麼調用者就一直循環在那裏看是否該自旋鎖的保持者已經釋放了鎖,“自旋”一詞就是因此而得名。這樣做的好處是減少了線程從睡眠到喚醒的資源消耗,但會一直佔用CPU的資源,適用於資源的鎖被持有的時間短,而又不希望在線程的喚醒上花費太多資源的情況。
  • 使用如下代碼可以初始化一個自旋鎖:

KSPIN_LOCK spinLock;
keInitlizeSpinLock(&spinLock);
//這個函數無返回值
  • 自選鎖的使用用到了兩個函數,KeAcquireSpinLock獲得一個自旋鎖也就是加鎖操作和KeRelseaseSpinLock釋放自旋鎖也就是解鎖。

    使用KeAcquireSpinLock時會提高當前中斷級別,舊的中斷級別需要定義一個KIRQL類型的變量進行保存,以便於重新設置回中斷級別。

  • 具體代碼如下:

//要注意,多線程使用自旋鎖同步時
//定義的自選鎖必須是一個全局的
//不然是沒有任何意義的
KSPIN_LOCK g_spinLock;
VOID MySafeThreadProc(PVOID context)
{
    //一個線程回調
   	KeInitializeSpinLock(&g_spinLock);
    KIRQL oldIrql;
   	KeAcquireSpinLock(&g_spinLock,&oldIrql);
    //do something ...
    KeReleaseSpinLock(&g_spinLock,oldIrql);
}
  • 這樣諸如前面講解的鏈表操作,在鏈表初始化後,就可以採用一些列加鎖的操作來代替普通的操作:
//普通的鏈表插入函數如下
InsertHeadList(&myListHead,(PLIST_ENTRY)&myFileInfo);
//加鎖後的操作方式如下:
ExInterlockedInsertHeadList(&myListHead,(PLIST_ENTERY)&myFileInfo,&g_spinLock);

//類似的還有一個移除節點的操作也可以改變
myFileInfo=ExInterlockedRemoveHeadList(&myListHead,&g_spinLock);

//都是在原先函數的基礎上加上了一個自旋鎖參數,用於保證線程安全

使用隊列自旋鎖提高性能

  • 隊列自旋鎖(Queued spin lock):是WindowsXP之後被引入的,他在多CPU平臺上具有更高的性能表現,並且遵守“first-come first-served”原則,即-“誰先等待,誰先獲取自旋鎖”的原則。
  • 隊列自旋鎖初始化和普通自旋鎖一樣使用KeInitializeSpinLock函數,不同之處在與獲取和釋放自旋鎖時需要使用新的函數:
VOID KeAcquireInStackQueuedSpinLock(
	IN PKSPIN_LOCK SpinLock,
    In PKLOCK_QUEUE_HANDLE LockHandle
);
VOID KeReleaseInStackQueuedSpinLock(
	IN PKLOCK_QUEUE_HANDLE LockHandle
);

//下面介紹用法:
//1. 初始化一個隊列自旋鎖
KSPIN_LOCK queueSpinLock={0};
KeInitializeSpinLock(&queueSpinLock);
//2. 隊列自旋鎖獲取
KLOCK_QUEUE_HANDLE lockQueueHandle;
KeAcquireInstackQueuedSpinLock(&queueSpinLock,&lockQueueHandle);
//do something...
//3. 釋放
KeReleaseInstackQueuedSpinLock(&lockQueueHandle);
  • 可以看出,隊列自旋鎖增加了一個KLOCK_QUEUE_HANDLE數據結構,這個結構唯一地表示一個隊列自旋鎖。
  • 雖然兩種自旋鎖都使用同一個初始化函數初始化,但是,兩者絕對不能混用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章