第二種計時器是 DPC 計時器, 這種計時器的使用比 I/O 計時器更加靈活.
DPC 計時器 :
在驅動程式中第二種使用計時器的方法是使用 DPC 計時器, 這種計時器可以對任何時間間隔進行定時. DPC 計時器內部使用物件 KTIMER. 當對計時器設定一個時間間隔後, 每隔這個時間作業系統就會將一個 DPC 常式插入 DPC 佇列. 當作業系統讀取 DPC 佇列時, 對應的 DPC 常式會被執行. DPC 計時器常式相當於計時器的回呼函式.
在此用 DPC 計時器前, 需要初始化 DPC 物件和計時器物件. 初始化計時器物件使用內核函式 KeInitializeTimer, 其宣告如下 :
其中參數 Timer 是計時器的指標, 這個初始化函式的名稱和 IoInitializeTimer 函式很類似, 千萬不要誤用了. 而初始化 DPC 物件的內核函式是 KeInitializeDpc, 其宣告如下 :
開啟計時器內核函式是 KeSetTimer, 其宣告如下 :
在呼叫 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 讓驅動程式開啟計時器和關閉計時器, 並告訴計時器的間隔時間 :
- ...
- DWORD dwOutput;
- DWORD dwMircoSeconds = 1000*1000*2;
- DeviceIoControl(hDevice, IOCTL_START_TIMER, &dwMircoSeconds, sizeof(DWORD), NULL, 0, &dwOutput, NULL);
- Sleep(10000);
- DeviceIoControl(hDevice, IOCTL_STOP_TIMER, NULL, 0, NULL, 0, &dwOutput, NULL);
- ...
- typedef struct _DEVICE_EXTENSION {
- PDEVICE_OBJECT pDevice;
- UNICODE_STRING ustrDeviceName; //裝置名稱
- UNICODE_STRING ustrSymLinkName; //符號連結名
- KDPC pollingDPC; // 儲存DPC物件
- KTIMER pollingTimer;// 儲存計時器物件
- LARGE_INTEGER pollingInterval; // 記錄計時器間隔時間
- } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
- KeInitializeTimer( &pDevExt->pollingTimer );
- KeInitializeDpc( &pDevExt->pollingDPC,
- PollingTimerDpc,
- (PVOID) pDevObj );
最後是編寫 DPC 常式, 每次執行 KeSetTimer 只會觸發一次 DPC 常式. 為了能週期地呼叫 DPC 常式, 應該在 DPC 常式中再次呼叫 KeSetTimer, 並且時間間隔保持不變. 需要注意的是 DPC 常式必須執行在 DISPATCH_LEVEL 的 IRQL 層級, 因此不能使用分頁記憶體, 並且在 DPC 常式的開始處用 #pragma LOCKEDCODE 修飾. 最後為了驗證常式可以執行在任意執行緒 Context 中, 加入了獲取當前處理程式碼的線程名稱作為 log 資訊輸出 :
下圖為執行結果 :
沒有留言:
張貼留言