1. 程式人生 > >核心下各種同步處理方法(自旋鎖、訊號燈、互斥體…)

核心下各種同步處理方法(自旋鎖、訊號燈、互斥體…)

轉自:http://www.blogfshare.com/kernel-synchronization.html

1.在支援多執行緒的作業系統下,有些函式會出現不可重入的現象。所謂“可重入”是指函式的執行結果不和執行順序有關。反之如果執行結果和執行順序有關,則稱這個函式是“不可重入”的。

2.Windows將中斷的概念進行了擴充套件,提出一箇中斷請求級(IRQL)的概念。其中規定了32箇中斷請求級別,分別是0~2級別為軟體中斷,3~31級為硬體中斷,其中數字從0~31,優先級別逐次遞增。

image

在核心模式下,可以通過呼叫KeGetCurrentIrql核心函式來得到當前的IRQL級別。

3.執行緒執行在PASSIVE_LEVEL,這個時候作業系統隨時可能將當前執行緒切換到別的執行緒,但是如果提升IRQL到DISPATCH_LEVEL級別,這時候不會出現執行緒切換。這是一種很常用的同步處理機制,但這種方法只能使用於單CPU的系統。對於多CPU的系統,需要採用別的同步處理機制。

4.讀取不在實體記憶體中的分頁記憶體時,會引發一個頁故障,從而執行異常的處理函式重新將磁碟的檔案內容交換到實體記憶體中。頁故障允許出現在PASSIVE_LEVEL級別的程式中,但如果在DISPATCH_LEVEL或者更高級別IRQL的程式中會帶來系統崩潰。對於等於或高於DISPATCH_LEVEL級別的程式不能使用分頁記憶體,必須使用非分頁記憶體。驅動程式的StartIO例程、DPC例程、中斷服務例程都執行在DISPATCH_LEVEL或者更高IRQL。因此,在這些例程中不能使用分頁記憶體,否則會導致系統崩潰。

5.控制IRQL提升與降低:KeRaiseIrql/KeLowerIrql

6.自旋鎖:不同於執行緒中的等待時間。線上程中如果等待某個事件,作業系統會使這個執行緒進入休眠狀態,CPU會執行其他執行緒,而自旋鎖原理則不同,他不會切換到別的執行緒,而是一直讓這個執行緒“自旋”。因此,對於自旋鎖佔用時間不宜過長,否則會導致申請自旋鎖的其他執行緒處於自旋,這會浪費CPU寶貴的時間。在單CPU的系統中,獲取自旋鎖只是將當前的IRQL從PASSVIE_LEVEL級別提升到DISPATCH_LEVEL級別。驅動程式必須在低於或者等於DISPATCH_LEVEL的IRQL級別中使用自旋鎖。

注意:如果在DISPATHC_LEVEL級別申請自旋鎖,那麼不會改變IRQL級別。這時,申請和釋放自旋鎖可以簡單的使用KeAcquireSpinLockAtDpcLevelKeReleaseSpinLockFromDpcLevel核心函式。

...

21.使用互鎖操作進行同步

DDK提供了兩類互鎖操作來提供簡單的同步處理。一類是InterlockedXX函式,另一類是ExInterlockedXX函式。

其中InterlockedXX函式不是通過自旋鎖實現的,內部不會提升IRQL,因此既可以操作分頁資料又可以操作非分頁資料。而ExInterlockedXX函式是通過自旋鎖實現的,在使用的時候需要我們提供一個自旋鎖。內部依靠這個自旋鎖實現同步,因此它不能操作分頁記憶體。

附註:

這段程式碼標紅的緣於這幾天看MS toaster程式碼ToasterCleanup函式,在函式的註釋部分發現一句:"Note that ToasterCleanup does not call the PAGED_CODE marco because the routine use a spin lock"。開始沒明白為什麼持有鎖的程式碼不能被分頁出去,後來看到網上這篇文章就明白了~最後再引用看雪上一段討論:

引用: 最初由 lidagogo釋出 檢視帖子 KeAcquireInStackQueuedSpinLock
在這裡執行了一些程序查詢 遠端寫入記憶體等操作 直接藍屏
KeReleaseInStackQueuedSpinLock

放到外面來做 怎麼都沒事 什麼鬼?

崩潰在這行
//DbgBreakPoint();

KeA...

第一,由於自旋鎖與佇列自旋鎖系列函式可能會把當前的 IRQL 從 PASSIVE_LEVEL 提升到 DISPATCH_LEVEL 。另一方面,你用 ZwAllocateVirtualMemory() 分配的緩衝區(pBuffer)可能隨時被換出記憶體,因此 memcpy() 訪問的時候可能已經被換出了,這會引發一個缺頁異常,而相應的
page falut handler() 無法在 DISPATCH_LEVEL 下完成,因此 pBuffer 指向的記憶體無法被換入,造成崩潰。
解決辦法是,pBuffer 應該用 ExAllocatePool/ExAllocatePoolWithTag() 來分配,可以為它的第一個引數傳入 NonPagePool 表示在核心空間的非換頁池中分配。這樣它就確保不會被換出記憶體。
第二,很多 Dbg*() 系列例程要求在 PASSIVE_LEVEL 下執行。獲得自旋鎖後,你可以用 KeGetCurrentIrql() 檢視當前 IRQL ,如果再不是 PASSIVE_LEVEL 了,就應該避免執行後續的
操作(包括呼叫 Dbg*());或者用 KeLowerIrql() 降低到 PASSIVE_LEVEL ,可以在一個 if 語句塊中完成檢測;
第三,也可能是你沒有初始化自旋鎖造成崩潰的,正確的使用方法如下:
使用自旋鎖:
// 包含 wdm.h ,其中有 KSPIN_LOCK 等資料結構的定義

KSPIN_LOCK get_spin_lock;
KIRQL old_irql; //用於儲存並恢復 IRQL
KeInitializeSpinLock(&get_spin_lock);
KeAcquireSpinLock(&get_spin_lock, &old_irql);
{....在這裡完成你的操作}
KeReleaseSpinLock(&get_spin_lock, old_irql);

使用排隊的自旋鎖:

KSPIN_LOCK get_spin_lock;
KLOCK_QUEUE_HANDLE lock_queue_handle;
KeInitializeSpinLock(&get_spin_lock);
KeAcquireInStackQueueSpinLock(&get_spin_lock, &lock_queue_handle);
{.....完成記憶體複製操作,記得處理 IRQL 不為 PASSIVE_LEVEL 時的情況}
KeReleaseInStackQueueSpinLock(&lock_queue_handle);

最後,如果上述方案都失效,還可以使用下列的頁面鎖定函式之一,根據你的實際情況來選擇:
MmProbeAndLockPages()
MmLockPagableCodeSection()
MmLockPagableDataSection()
MmLockPagableSectionByHandle()
頁面鎖定使用這種機制將你的 pBuffer 保留在實體記憶體中(不會被換出),直到使用 MmUnlockPages() 顯式解鎖。