自旋鎖

[返回] [上一頁] [下一頁]

自旋鎖


IRQL概念僅能解決單CPU上的同步問題,在多處理器平臺上,它不能保證你的代碼不被運行在其它處理器上的代碼所幹擾。一個稱爲自旋鎖(spin lock)的原始對象可以解決這個問題。爲了獲得一個自旋鎖,在某CPU上運行的代碼需先執行一個原子操作,該操作測試並設置(test-and-set)某個內存變量,由於它是原子操作,所以在該操作完成之前其它CPU不可能訪問這個內存變量。如果測試結果表明鎖已經空閒,則程序獲得這個自旋鎖並繼續執行。如果測試結果表明鎖仍被佔用,程序將在一個小的循環內重複這個“測試並設置(test-and-set)”操作,即開始“自旋”。最後,鎖的所有者通過重置該變量釋放這個自旋鎖,於是,某個等待的test-and-set操作向其調用者報告鎖已釋放。

關於自旋鎖有兩個明顯的事實。第一,如果一個已經擁有某個自旋鎖的CPU想第二次獲得這個自旋鎖,則該CPU將死鎖(deadlock)。自旋鎖沒有與其關聯的“使用計數器”或“所有者標識”;鎖或者被佔用或者空閒。如果你在鎖被佔用時獲取它,你將等待到該鎖被釋放。如果碰巧你的CPU已經擁有了該鎖,那麼用於釋放鎖的代碼將得不到運行,因爲你使CPU永遠處於“測試並設置”某個內存變量的自旋狀態。

關於自旋鎖的另一個事實是,CPU在等待自旋鎖時不做任何有用的工作,僅僅是等待。所以,爲了避免影響性能,你應該在擁有自旋鎖時做盡量少的操作,因爲此時某個CPU可能正在等待這個自旋鎖。

關於自旋鎖還存在着一個不太明顯但很重要的事實:你僅能在低於或等於DISPATCH_LEVEL級上請求自旋鎖,在你擁有自旋鎖期間,內核將把你的代碼提升到DISPATCH_LEVEL級上運行。在內部,內核能在高於DISPATCH_LEVEL的級上獲取自旋鎖,但你和我都做不到這一點。

 

使用自旋鎖

爲了明確地使用一個自旋鎖,首先要在非分頁內存中爲一個KSPIN_LOCK對象分配存儲。然後調用KeInitializeSpinLock初始化這個對象。接着,當代碼運行在低於或等於DISPATCH_LEVEL級上時獲取這個鎖,並執行需要保護的代碼,最後釋放自旋鎖。例如,假設你的設備擴展中有一個名爲QLock的自旋鎖,你用它來保護你專用IRP隊列的訪問。你應該在AddDevice函數中初始化這個鎖:

typedef struct _DEVICE_EXTENSION {
  ...
  KSPIN_LOCK QLock;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

...

NTSTATUS AddDevice(...)
{
  ...
  PDEVICE_EXTENSION pdx = ...;
  KeInitializeSpinLock(&pdx->QLock);
  ...
}

在驅動程序的其它地方,假定就在某種IRP的派遣函數中,你在某些必須的隊列操作代碼周圍獲取了(並很快釋放)該自旋鎖。注意這個函數必須存在於非分頁內存中,因爲在某個時期它會執行在提升的IRQL上。

NTSTATUS DispatchSomething(...)
{
  KIRQL oldirql;
  PDEVICE_EXTENSION pdx = ...;
  KeAcquireSpinLock(&pdx->QLock, &oldirql);				<--1
  ...
  KeReleaseSpinLock(&pdx->QLock, oldirql);				<--2
}
  1. KeAcquireSpinLock獲取自旋鎖時,它也把IRQL提升到DISPATCH_LEVEL級上。
  2. KeReleaseSpinLock釋放自旋鎖時,它也把IRQL降低到原來的IRQL級上。

如果你知道代碼已經處在DISPATCH_LEVEL級上,你可以調用兩個專用函數來獲取自旋鎖。這個技術適合於DPC、StartIo,和其它執行在DISPATCH_LEVEL級上的驅動程序例程:

KeAcquireSpinLockAtDpcLevel(&pdx->QLock);
...
KeReleaseSpinLockFromDpcLevel(&pdx->QLock);
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章