程式扎記: [ Windows DDP ] 計時器 : 計時器實作方式一

標籤

2011年3月3日 星期四

[ Windows DDP ] 計時器 : 計時器實作方式一

前言 : 
在驅動程式中, 一般有兩種方式使用計時器, 一種方法是使用 I/O 計時器常式, 另一種方法是使用 DPC 常式. 這裡將介紹如何使用 I/O 計時器常式. 

I/O 計時器 : 
I/O 計時器是 DDK 提供的一種計時器. 使用這種計時器, 每間隔 1s 鐘系統會呼叫一次 I/O 計時器常式. 設計師可以對這種計時器稍加改進, 在計時器常式中記錄某一個計數, 初始值設置為 N. 每次進入計時器的時候計數減一, 當計數歸零時, 執行某項操作, 並將計數還原為 N. 這樣間隔 1s 的計時器就變化為間隔 N s的計時器. 
I/O 計時器可以間隔 Ns 做定時, 但如果要實作對 ms 層級間隔甚至更小單位, 則需要用到另一種計時機制 (下一節介紹的 DPC 計時器). 在使用 I/O 計時器前需要先進行初始化, 初始化 I/O 計時器使用內核函式 IoInitializeTimer, 其宣告如下 : 

- Syntax :
  1. NTSTATUS IoInitializeTimer(  
  2.   __in      PDEVICE_OBJECT DeviceObject,  
  3.   __in      PIO_TIMER_ROUTINE TimerRoutine,  
  4.   __in_opt  PVOID Context  
  5. );  

- 參數說明 :
* 參數 DeviceObject : 這個參數是 I/O 計時器關聯的裝置物件指標
* 參數 TimerRoutine : 這個參數是 I/O 計時器關聯的計時器常式
* 參數 Context : 這個參數是傳進計時器常式的參數.

在初始化 I/O 計時器後, 可以開啟與停止 I/O 計時器. 開啟計時器後, 每隔一秒, 系統呼叫一次計時器常式. 在停止計時器後, 系統就不會進入計時器常式. 開啟計時器的內核函式是IoStartTimer, 停止 I/O 計時器的內核函式是 IoStopTimer. 
需要指出的是, I/O 計時器常式執行在 DISPATCH_LEVEL 層級, 因此在這個常式中不能使用分頁記憶體, 否則會引起頁故障而導致系統崩潰. 另外 I/O 計時器是執行在任意執行緒的, 不一定是 IRP 發起的執行緒中, 因此不能直接使用應用程式的記憶體. 

示例程式碼 : 
下面的例子演示了如何在驅動程式中使用 I/O 計時器. 這個例子每隔一秒進入 I/O 計時器常式, 每隔 3 秒輸出一行除錯資訊. 首先在 DriverEntry 中對 I/O 計時器進行初始化 : 
  1. ...  
  2. IoInitializeTimer(pDeviceObj, OnTimer, NULL);  
  3. ...  
然後在裝置擴充中, 加入一個計數變數, 這個變數負責記錄間隔的秒數 : 
  1. typedef struct _DEVICE_EXTENSION {  
  2. ...  
  3.     LONG lTimerCount;    // 計數變數  
  4. ...  
  5. } DEVICE_EXTENSION, *PDEVICE_EXTENSION;  
然後定義兩個 IOCTL 碼, 分別是 IOCTL_START_TIMER 和 IOCTL_STOP. 當應用程式發起這兩個 IOCTL 請求後, 分別開啟與關閉計時器 : 
- Driver.cpp (函式 HelloDDKDeviceIOControl) :
  1. #pragma PAGEDCODE  
  2. NTSTATUS HelloDDKDeviceIOControl(IN PDEVICE_OBJECT pDevObj,  
  3.                                  IN PIRP pIrp)  
  4. {  
  5.     NTSTATUS status = STATUS_SUCCESS;  
  6.     KdPrint(("Enter HelloDDKDeviceIOControl\n"));  
  7.   
  8.     //得到當前堆疊  
  9.     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);  
  10.     //得到輸入緩衝區大小  
  11.     ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;  
  12.     //得到輸出緩衝區大小  
  13.     ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;  
  14.     //得到IOCTL碼  
  15.     ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;  
  16.   
  17.     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  18.         pDevObj->DeviceExtension;  
  19.   
  20.     ULONG info = 0;  
  21.   
  22.     switch (code)  
  23.     {                       // process request  
  24.         case IOCTL_START_TIMER:  
  25.         {  
  26.             KdPrint(("IOCTL_START_TIMER\n"));  
  27.             pDevExt->lTimerCount = TIMER_OUT;  
  28.             IoStartTimer(pDevObj);  
  29.             break;  
  30.         }  
  31.         case IOCTL_STOP:  
  32.         {  
  33.             KdPrint(("IOCTL_STOP\n"));  
  34.             IoStopTimer(pDevObj);  
  35.             break;  
  36.         }  
  37.         default:  
  38.             status = STATUS_INVALID_VARIANT;  
  39.     }  
  40.   
  41.     // 完成IRP  
  42.     pIrp->IoStatus.Status = status;  
  43.     pIrp->IoStatus.Information = info;   // bytes xfered  
  44.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  45.   
  46.     KdPrint(("Leave HelloDDKDeviceIOControl\n"));  
  47.   
  48.     return status;  
  49. }  

下面編寫計時常式, 這個常式是執行在 DISPATCH_LEVEL 的 IRQL 層級. 首先注意這個常式中不能使用分頁計憶體, 另外在函式首部要使用 #pragma LOCKEDCODE. 為了證明該執行緒可以執行在任意執行緒 Context, 在這個常式的最後, 我們得到當前執行緒的名稱, 並用 log 資訊將其輸出 : 
- Driver.cpp (函式 OnTimer) :
  1. #pragma LOCKEDCODE  
  2. VOID OnTimer(  
  3.     IN PDEVICE_OBJECT DeviceObject,  
  4.     IN PVOID Context)  
  5. {  
  6.     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  7.         DeviceObject->DeviceExtension;  
  8.     KdPrint(("Enter OnTimer!\n"));  
  9.   
  10.     //將計數器自鎖減一  
  11.     InterlockedDecrement(&pDevExt->lTimerCount);  
  12.       
  13.     //如果計數器減到0,重新程式設計TIMER_OUT,整個過程是互鎖運算  
  14.     LONG previousCount = InterlockedCompareExchange(&pDevExt->lTimerCount,TIMER_OUT,0);  
  15.   
  16.     //每隔三秒,計數器一個迴圈,輸出以下log  
  17.     if (previousCount==0)  
  18.     {  
  19.         KdPrint(("%d seconds time out!\n",TIMER_OUT));  
  20.     }  
  21.   
  22.     //證明該執行緒運行在任意執行緒context的  
  23.     PEPROCESS pEProcess = IoGetCurrentProcess();  
  24.      
  25.     PTSTR ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);//即可得到使用者處理程序  
  26.   
  27.     KdPrint(("The current process is %s\n",ProcessName));  
  28. }  

接著在 User mode 程序發起 IOCTL 呼叫 : 
- main.cpp : 發出 IOCTL:IOCTL_START_TIMER 呼叫, 並於 5秒後再次發出 IOCTL:IOCTL_STOP 呼叫
  1. #include   
  2. #include   
  3. //使用CTL_CODE必須加入winioctl.h  
  4. #include   
  5. #include "..\NT_Driver\Ioctls.h"  
  6.   
  7. int main()  
  8. {  
  9.     HANDLE hDevice =   
  10.         CreateFile("\\\\.\\HelloDDK",  
  11.                     GENERIC_READ | GENERIC_WRITE,  
  12.                     0,      // share mode none  
  13.                     NULL,   // no security  
  14.                     OPEN_EXISTING,  
  15.                     FILE_ATTRIBUTE_NORMAL,  
  16.                     NULL );     // no template  
  17.   
  18.     if (hDevice == INVALID_HANDLE_VALUE)  
  19.     {  
  20.         printf("Failed to obtain file handle to device: "  
  21.             "%s with Win32 error code: %d\n",  
  22.             "MyWDMDevice", GetLastError() );  
  23.         return 1;  
  24.     }  
  25.   
  26.     DWORD dwOutput;  
  27.   
  28.     DeviceIoControl(hDevice, IOCTL_START_TIMER, NULL, 0, NULL, 0, &dwOutput, NULL);  
  29.   
  30.     Sleep(5000);  
  31.   
  32.     DeviceIoControl(hDevice, IOCTL_STOP, NULL, 0, NULL, 0, &dwOutput, NULL);  
  33.   
  34.     CloseHandle(hDevice);  
  35.   
  36.     return 0;  
  37. }  

下圖為執行後 log 輸出結果, 每個 1s 進入一次 I/O 計時器常式, 另外實做了每隔 3s 輸出一行除錯資訊. I/O 計時器常式還列出當前的處理程序名, 可以發現呼叫函式 OnTimer 的並不是發起 IOCTL 請求的處理程序. 
Idle 處理程序和系統處理程序一樣, 都是一種特殊處理程序, 他沒有對應的 exe 檔與之對應, 在 Windows 啟動後這兩個程序就存在於系統中了. 

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!