2011年3月6日 星期日

[ Windows DDP ] 計時器 : 計時器實作方式二 (DPC 計時器)

前言 : 
第二種計時器是 DPC 計時器, 這種計時器的使用比 I/O 計時器更加靈活. 

DPC 計時器 : 
在驅動程式中第二種使用計時器的方法是使用 DPC 計時器, 這種計時器可以對任何時間間隔進行定時. DPC 計時器內部使用物件 KTIMER. 當對計時器設定一個時間間隔後, 每隔這個時間作業系統就會將一個 DPC 常式插入 DPC 佇列. 當作業系統讀取 DPC 佇列時, 對應的 DPC 常式會被執行. DPC 計時器常式相當於計時器的回呼函式. 
在此用 DPC 計時器前, 需要初始化 DPC 物件和計時器物件. 初始化計時器物件使用內核函式 KeInitializeTimer, 其宣告如下 : 

- Syntax :
  1. VOID KeInitializeTimer(  
  2.   __out  PKTIMER Timer  
  3. );  

其中參數 Timer 是計時器的指標, 這個初始化函式的名稱和 IoInitializeTimer 函式很類似, 千萬不要誤用了. 而初始化 DPC 物件的內核函式是 KeInitializeDpc, 其宣告如下 : 
- Syntax :
  1. VOID KeInitializeDpc(  
  2.   __out     PRKDPC Dpc,  
  3.   __in      PKDEFERRED_ROUTINE DeferredRoutine,  
  4.   __in_opt  PVOID DeferredContext  
  5. );  

參數說明 :
* 參數 Dpc : 這個參數是初始化的 DPC 物件指標.
* 參數 DeferredRoutine : 這個參數是與 DPC 關聯的 DPC 常式. 每當計時器間隔到時, 作業系統就會將一個 DPC 物件插入佇列, 並觸發該 DPC 常式執行.
* 參數 DeferredContext : 這個參數是傳入 DPC 常式的參數 

開啟計時器內核函式是 KeSetTimer, 其宣告如下 : 
- Syntax :
  1. BOOLEAN KeSetTimer(  
  2.   __inout   PKTIMER Timer,  
  3.   __in      LARGE_INTEGER DueTime,  
  4.   __in_opt  PKDPC Dpc  
  5. );  

參數說明 :
* 參數 Timer : 這個參數是計時器物件指標.
* 參數 DueTime : 這個參數設定時間間隔.
* 參數 Dpc : 這個參數是傳入 DPC 常式的參數.

在呼叫 KeSetTimer 函式後, 經過 DueTime 時間, 作業系統會呼叫 DPC 常式. 如果 DueTime 是正數, 則代表絕對時間, 該時間是從 1601年的 1 月 1 日到觸發 DPC 常式的那個時刻, 時間單位是 100ns. 如果 DueTime 是負數, 意味著間隔多長時間, 時間單位同樣是 100ns. 
在呼叫 KeSetTimer 後, 只會觸發一次 DPC 常式. 如果想要週期的觸發 DPC 常式, 需要在 DPC 常式被觸發後, 再次呼叫 KeSetTimer 函式. 

示例程式碼 : 
下面代碼演示了如何在驅動程式中使用 DPC 計時器. 這個例子定義了 IOCTL_START_TIMER 和 IOCTL_STOP_TIMER 兩個 IOCTL, 分別對應開啟計時器和關閉計時器. 在傳遞 IOCTL_START_TIMER 時, 將間隔時間從應用程式傳遞到驅動程式中, 這樣時間間隔是由應用程式控制的. 在應用程式可以呼叫 DeviceIoControl 讓驅動程式開啟計時器和關閉計時器, 並告訴計時器的間隔時間 : 
  1. ...  
  2.     DWORD dwOutput;  
  3.     DWORD dwMircoSeconds = 1000*1000*2;  
  4.     DeviceIoControl(hDevice, IOCTL_START_TIMER, &dwMircoSeconds, sizeof(DWORD), NULL, 0, &dwOutput, NULL);  
  5.     Sleep(10000);  
  6.     DeviceIoControl(hDevice, IOCTL_STOP_TIMER, NULL, 0, NULL, 0, &dwOutput, NULL);  
  7. ...  
在驅動程式中, 可以在裝置擴充中加入 DPC 物件和計時器物件 : 
  1. typedef struct _DEVICE_EXTENSION {  
  2.     PDEVICE_OBJECT pDevice;  
  3.     UNICODE_STRING ustrDeviceName;  //裝置名稱  
  4.     UNICODE_STRING ustrSymLinkName; //符號連結名  
  5.   
  6.     KDPC pollingDPC;    // 儲存DPC物件  
  7.     KTIMER pollingTimer;// 儲存計時器物件  
  8.     LARGE_INTEGER pollingInterval;  // 記錄計時器間隔時間  
  9. } DEVICE_EXTENSION, *PDEVICE_EXTENSION;  
在初始化 DPC 物件時, 需要將裝置物件作為參數傳遞給 DPC 常式. 這樣在 DPC 常式中可以透過裝置物件指標獲得間隔時間, 因為時間間隔記錄在裝置擴充中 : 
  1. KeInitializeTimer( &pDevExt->pollingTimer );  
  2. KeInitializeDpc( &pDevExt->pollingDPC,  
  3.                     PollingTimerDpc,  
  4.                     (PVOID) pDevObj );  
在 IRP_MJ_DEVICE_CONTROL 的派遣函式, 接收 IOCTL_START_TIMER 後, 可以將應用程式傳遞進來的間隔時間記錄下來, 並且呼叫 KeSetTimer 函式開啟計時器. 在接收 IOCTL_STOP_TIMER 後, 應該呼叫 KeCancelTimer 函式停止計時器 : 
- Driver.cpp (函式 HelloDDKDeviceIOControl) :
  1. NTSTATUS HelloDDKDeviceIOControl(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)  
  3. {  
  4.     NTSTATUS status = STATUS_SUCCESS;  
  5.     KdPrint(("Enter HelloDDKDeviceIOControl\n"));  
  6.   
  7.     //得到當前堆疊  
  8.     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);  
  9.     //得到輸入緩衝區大小  
  10.     ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;  
  11.     //得到輸出緩衝區大小  
  12.     ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;  
  13.     //得到IOCTL碼  
  14.     ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;  
  15.   
  16.     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  17.         pDevObj->DeviceExtension;  
  18.   
  19.     ULONG info = 0;  
  20.   
  21.     switch (code)  
  22.     {                       // process request  
  23.         case IOCTL_START_TIMER:  
  24.         {   
  25.             KdPrint(("IOCTL_START_TIMER!\n"));  
  26.   
  27.             //從使用者模式傳進來的超時  
  28.             ULONG ulMircoSeconds = *(PULONG)pIrp->AssociatedIrp.SystemBuffer;  
  29.   
  30.             pDevExt->pollingInterval = RtlConvertLongToLargeInteger( ulMircoSeconds * -10 );  
  31.   
  32.             KeSetTimer(  
  33.                 &pDevExt->pollingTimer,  
  34.                 pDevExt->pollingInterval,  
  35.                 &pDevExt->pollingDPC );  
  36.             break;  
  37.         }  
  38.         case IOCTL_STOP_TIMER:  
  39.         {   
  40.             KdPrint(("IOCTL_STOP_TIMER!\n"));  
  41.   
  42.             KeCancelTimer(&pDevExt->pollingTimer);  
  43.   
  44.             break;  
  45.         }  
  46.         default:  
  47.             status = STATUS_INVALID_VARIANT;  
  48.     }  
  49.   
  50.     // 完成IRP  
  51.     pIrp->IoStatus.Status = status;  
  52.     pIrp->IoStatus.Information = info;   // bytes xfered  
  53.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  54.   
  55.     KdPrint(("Leave HelloDDKDeviceIOControl\n"));  
  56.   
  57.     return status;  
  58. }  

最後是編寫 DPC 常式, 每次執行 KeSetTimer 只會觸發一次 DPC 常式. 為了能週期地呼叫 DPC 常式, 應該在 DPC 常式中再次呼叫 KeSetTimer, 並且時間間隔保持不變. 需要注意的是 DPC 常式必須執行在 DISPATCH_LEVEL 的 IRQL 層級, 因此不能使用分頁記憶體, 並且在 DPC 常式的開始處用 #pragma LOCKEDCODE 修飾. 最後為了驗證常式可以執行在任意執行緒 Context 中, 加入了獲取當前處理程式碼的線程名稱作為 log 資訊輸出 : 
- Driver.cpp (函式 PollingTimerDpc) :
  1. #pragma LOCKEDCODE  
  2. VOID PollingTimerDpc( IN PKDPC pDpc,  
  3.                       IN PVOID pContext,  
  4.                       IN PVOID SysArg1,  
  5.                       IN PVOID SysArg2 )   
  6. {  
  7.     PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pContext;  
  8.     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;  
  9.     KeSetTimer(  
  10.         &pdx->pollingTimer,  
  11.         pdx->pollingInterval,  
  12.         &pdx->pollingDPC );  
  13.     KdPrint(("PollingTimerDpc\n"));  
  14.   
  15.     //檢驗是運行在任意執行緒上下文  
  16.     PEPROCESS pEProcess = IoGetCurrentProcess();  
  17.      
  18.     PTSTR ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);  
  19.   
  20.     KdPrint(("%s\n",ProcessName));  
  21. }  

下圖為執行結果 : 

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...