程式扎記: [ Windows DDP ] 驅動程式的同步處理 : 核心模式下的同步物件 - 等待與執行緒開啟

標籤

2011年1月23日 星期日

[ Windows DDP ] 驅動程式的同步處理 : 核心模式下的同步物件 - 等待與執行緒開啟


前言 :
在核心模式下, 有一系列的同步物件與使用者模式下的同步物件相對應. 在使用者模式下, 各個函式都是以控制碼操作同步物件的. 而在使用者模式下, 程式設計師無法獲得真實同步物件的指標, 而用一個控制碼代表這個物件. 在核心模式下, 程式設計師可以獲得真實同步物件的指標.
每種同步物件在內核中都會對應一種資料結構. 因為在核心模式下, 程式設計師可以很自由地操作這些物件, 因此很可能因為不小心造成系統的鎖死.

核心模式下的等待 :
在核心模式下, 同樣也有兩個函式負責等待內核同步物件, 分別是 KeWaitForSingleObject 和 KeWaitForMultipleObjects 函式. 其實使用者模式下的 WaitForSingleObject 和 WaitForMultipleObjects 函式都是依靠核心模式下的這兩支函式實現的. KeWaitForSingleObject 函式負責等待單個同步物件, 其宣告如下 :
- Syntax :
  1. NTSTATUS KeWaitForSingleObject(  
  2.   __in      PVOID Object,  
  3.   __in      KWAIT_REASON WaitReason,  
  4.   __in      KPROCESSOR_MODE WaitMode,  
  5.   __in      BOOLEAN Alertable,  
  6.   __in_opt  PLARGE_INTEGER Timeout  
  7. );  

核心模式下的 KeWaitForSingleObject 函式比使用者模式下的 WaitForSingleObject 函式多了一些參數, 說明如下 :
* 第一個參數 Object 是一個同步物件的指標, 注意這裡不是控制碼.
* 第二個參數 WaitReason 表示等待的原因, 一般設為 Executive
* 第三個參數 WaitMode 是等待模式, 說明這個函式是在使用者模式下等待還是在核心模式下等待, 在這裡一般設為 KernelMode.
* 第四個參數 Alertable 一般設為 FALSE.
* 最後一個參數是等待的時間. 如果這個參數設為 NULL, 則代表無限期等待. 直到等待的同步物件變為激發狀態

如果等待的同步物件變為激發狀態, 這個函式會退出睡眠狀態, 並返回 STATUS_SUCCESS. 如果這個函式是因為超時而退出, 則會返回 STATUS_TIMEOUT.
KeWaitForMultipleObjects 負責在核心模式下等待多個同步物件, 其宣告如下 :
- Syntax :
  1. NTSTATUS  KeWaitForMultipleObjects(  
  2.   __in       ULONG Count,  
  3.   __in       PVOID Object[],  
  4.   __in       WAIT_TYPE WaitType,  
  5.   __in       KWAIT_REASON WaitReason,  
  6.   __in       KPROCESSOR_MODE WaitMode,  
  7.   __in       BOOLEAN Alertable,  
  8.   __in_opt   PLARGE_INTEGER Timeout,  
  9.   __out_opt  PKWAIT_BLOCK WaitBlockArray  
  10. );  

第一個參數 Count 為等待同步物件的個數, 第二個參數 Object 是同步物件的陣列, 第三個參數 WaitType 指示是等待任意一個同步物件還是等待所有的同步物件. 剩下的參數 WaitType, WaitReason, WaitMode 等作用同 KeWaitForSingleObject 參數說明.
如果這個函式是因為超時而退出, 則返回 STATUS_TIMEOUT. 如果是因為陣列中其中一個同步物件變為激發狀態, 這個函式返回的狀態碼減去 STATUS_WAIT_0, 就是激發的同步物件在陣列中的索引號. 下面範例代碼說明如何使用 KeWaitForMultipleObjects 函式 :
- 範例代碼 : 使用 KeWaitForMultipleObjects 獲取激發物件的索引
  1. NTSTATUS status = KeWaitForMultipleObjects(...);  
  2. if(NT_SUCCESS(status)) {  
  3.     ULONG index = status = STATUS_WAIT_0; // 得到激發物件在陣列的索引  
  4. }  

核心模式下開啟多執行緒 :
一般在多執行緒中才需要同步處理機制, 這裡介紹一下如何在核心模式下新建執行緒. 內核函式 PsCreateSystemThread 負責新建執行緒. 該函式可以新建兩種執行緒. 一種是使用者執行緒, 一種是系統執行緒. 使用者執行緒是屬於當前處理程序中的執行緒. 當前處理程序指的是當前 I/O 操作的發起者. 如果在 IRP_MJ_READ 的派遣函式中呼叫 PsCreateSystemThread 函式新建使用者執行緒, 新執行緒就屬於呼叫 ReadFile 的處理程序.
系統執行緒不屬於當前使用者處理程序, 而屬於系統處理程序. 系統處理程序是作業系統中一個特殊的處理程序. 這個處理程序的 PID 一般為 4, 讀者可以用工作管理員查看該處理程序, 如下圖所示 :

驅動程式中的 DriverEntry 和 AddDevice 等函式都是被某個系統執行緒呼叫的. PsCreateSystemThread 函式宣告如下 :
- Syntax :
  1. NTSTATUS PsCreateSystemThread(  
  2.   __out      PHANDLE ThreadHandle,  
  3.   __in       ULONG DesiredAccess,  
  4.   __in_opt   POBJECT_ATTRIBUTES ObjectAttributes,  
  5.   __in_opt   HANDLE ProcessHandle,  
  6.   __out_opt  PCLIENT_ID ClientId,  
  7.   __in       PKSTART_ROUTINE StartRoutine,  
  8.   __in_opt   PVOID StartContext  
  9. );  

參數說明 :
* ThreadHandle : 用於輸出, 這個參數得到新建執行緒控制碼.
* DesiredAccess : 新建執行緒的權限
* ObjectAttributes : 執行緒的屬性, 一般設為 NULL
* ProcessHandle : 指定新建的執行緒是使用者執行緒還是系統處理程序. 如果該值為 NULL, 則為系統執行緒. 如果該值是一個處理程序的控制碼, 則新建的執行緒屬於這個指定處理程序. DDK 提供巨集 NtCurrentProcess 得到當前處理程序的控制碼.
* StartRoutine : 新執行緒的執行位址.
* StartContext : 新執行緒接收的參數

在核心模式下, 新建的執行緒必須用函式 PsTerminateSystemThread 強制結束執行緒. 否則該執行緒是無法自動退出的. 這裡介紹一種方法可以方便的讓執行緒知道自己屬於哪個處理程序. 首先使用 IoGetCurrentProcess 函式得到當前執行緒. IoGetCurrentProcess 含是會得到一個 PEPROCESS 資料結構. 該資料結構記錄處理程序的資訊, 其中包括處理程序名. 遺憾的是微軟並沒有在 DDK 中定義 PEPROCESS 結構, 但可以利用微軟的符號表分析這個資料結構. 一般使用 WinDbg 查看這個資料結構. 在 Windows XP 中, PEPROCESS 結構的 0x174 偏移位置記錄著處理程序名. 因此可以用下面方法查看當前處理程序名稱.
  1. PEPROCESS pEProcess = IoGetCurrentProcess();  
  2. PTSTR ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);  
  3. KdPrint(("This thread run in %s process!\n", ProcessName));   
下面的程序碼展示如何在驅動程式中新建執行緒. 在例子中分別新建使用者執行緒和系統執行緒. 執行緒還顯示出各自所屬的處理程序名, 代碼如下 :
- Driver.cpp : 示範如何在核心模式下建立 Thread
  1. VOID SystemThread(IN PVOID pContext)  
  2. {  
  3.     KdPrint(("Enter SystemThread\n"));  
  4.     PEPROCESS pEProcess = IoGetCurrentProcess();  
  5.     PTSTR ProcessName = (PTSTR)((ULONG)pEProcess+0x174);  
  6.   
  7.     KdPrint(("This thread is running in %s process!\n"), ProcessName);  
  8.     KdPrint(("Leave SystemThread\n"));  
  9.     PsTerminateSystemThread(STATUS_SUCCESS);  
  10. }  
  11.   
  12.   
  13. VOID MyProcessThread(IN PVOID pContext)  
  14. {  
  15.     KdPrint(("Enter MyProcessThread\n"));  
  16.     PEPROCESS pEProcess = IoGetCurrentProcess();  
  17.     PTSTR ProcessName = (PTSTR)((ULONG)pEProcess+0x174);  
  18.   
  19.     KdPrint(("This thread is running in %s process!\n"), ProcessName);  
  20.     KdPrint(("Leave MyProcessThread\n"));  
  21.     PsTerminateSystemThread(STATUS_SUCCESS);  
  22. }  
  23.   
  24.   
  25. VOID CreateThread_Test()   
  26. {  
  27.     HANDLE hSystemThread, hMyThread;  
  28.     // 新建系統執行緒, 該執行緒是 System 處理程序的執行緒  
  29.     NTSTATUS status = PsCreateSystemThread(&hSystemThread, 0, NULL, NULL, NULL, SystemThread, NULL);  
  30.     // 新建處理程序執行緒, 該執行緒是使用者處理程序的執行緒  
  31.     status = PsCreateSystemThread(&hMyThread, 0, NULL, NtCurrentProcess(), NULL, MyProcessThread, NULL);  
  32. }  
  33. ...  

第二個執行緒是使用者執行緒, 它是在 IRP_MJ_CONTROL 的派遣函式中新建的. 因此這個執行緒屬於 DeviceIOControl 所在的處理程序, 也就是 DDPWraper.exe 處理程序. 第一個新建的執行緒是系統執行緒, 驅動程式輸出 log 資訊如下圖 :
This message was edited 10 times. Last update was at 07/01/2011 15:46:12

沒有留言:

張貼留言

網誌存檔