《Windows驅動開發技術詳解》之定時器

  • I/O定時器

I/O定時器是DDK提供的一種定時器。它每一個1s鍾系統會調用一次I/O定時器例程。I/O定時器例程運行在DISPATCH_LEVEL級別,所以在這個例程中不能使用分頁內存,不然會引發頁故障從而致使系統崩潰。另外I/O定時器是運行在任一線程的,不必定是IRP發起的線程中,所以不能直接使用應用程序的內存地址。函數

初始化I/O定時器後,能夠開啓和中止I/O定時器。開啓定時器後,每一個1s系統調用一次定時器例程。在聽指定是氣候,系統就不會進入定時器例程。開啓定時器的內核函數是IoStartTimer,中止I/O定時器的內核函數是IoStopTimer。spa

示例代碼:操作系統

如今DriverEntry中初始化計時器:線程

再寫相應的派遣函數:code

 1 NTSTATUS HelloDDKDeviceIoControl_Timer(PDEVICE_OBJECT pDevObj, PIRP pIrp){
 2     DbgPrint("Enter HelloDDKDeviceIoControl_Timer!\n");
 3     NTSTATUS status = STATUS_SUCCESS;
 4     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
 5     //ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;
 6     //ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;
 7     ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
 8     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
 9         pDevObj->DeviceExtension;
10     ULONG info = 0;
11     switch (code)
12     {
13     case IOCTL_START_TIMER:
14     {
15         DbgPrint("IOCTL_START_TIMER\n");
16         pDevExt->lTimerCount = TIMER_OUT;
17         IoStartTimer(pDevObj);
18         break;
19     }
20     case IOCTL_STOP:
21     {
22         DbgPrint("IOCTL_STOP\n");
23         IoStopTimer(pDevObj);
24         break;
25     }
26     default:
27         status = STATUS_INVALID_VARIANT;
28     }
29     pIrp->IoStatus.Status = status;
30     pIrp->IoStatus.Status = info;
31     IoCompleteRequest(pIrp, IO_NO_INCREMENT);
32     DbgPrint("Leave HelloDDKDeviceIoControl_Timer!\n");
33     return status;
34 }

再寫入Timer例程:orm

應用層代碼以下:對象

發送相應控制碼到底層,每三秒輸出一個「Time Out」:blog

這個例子忘記中止計時器,則會一直輸出下去。隊列

  • DPC定時器

 驅動程序中第二種使用定時器的方法是使用DPC定時器,這種定時器更加靈活,能夠對任意間隔時間進行定時。DPC定時器內部使用定時器對象KTIMER,當對定時器設定一個時間間隔後,每隔這段時間操做系統就會將一個DPC例程插入DPC隊列。當操做系統讀取DPC隊列時,對應的DPC例程會被執行。DPC定時器例程至關於定時器的回調函數。內存

 示例代碼以下:

如今設備擴展中添加幾項:

派遣函數以下:

 1 NTSTATUS HelloDDKDeviceIoControl_DPC(PDEVICE_OBJECT pDevObj, PIRP pIrp){
 2     DbgPrint("Enter HelloDDKDeviceIoControl_DPC!\n");
 3     NTSTATUS status = STATUS_SUCCESS;
 4     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
 5     ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
 6     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
 7         pDevObj->DeviceExtension;
 8     ULONG info = 0;
 9     switch (code){
10     case IOCTL_START_TIMER:
11     {
12         DbgPrint("IOCTL_START_TIMER!\n");
13         ULONG ulMircoSeconds = *(PULONG)pIrp->AssociatedIrp.SystemBuffer;
14         pDevExt->pollingInterval = RtlConvertLongToLargeInteger(ulMircoSeconds*-10);
15         KeSetTimer(&pDevExt->pollingTimer,
16             pDevExt->pollingInterval,
17             &pDevExt->pollingDPC);
18         break;
19     }
20     case IOCTL_STOP:
21     {
22         DbgPrint("IOCTL_STOP!\n");
23         KeCancelTimer(&pDevExt->pollingTimer);
24         break;
25     }
26     default:
27         status = STATUS_INVALID_VARIANT;
28     }
29     pIrp->IoStatus.Status = status;
30     pIrp->IoStatus.Information = info;
31     IoCompleteRequest(pIrp, IO_NO_INCREMENT);
32     DbgPrint("Leave HelloDDKDeviceIoControl_DPC!\n");
33     return status;
34 }

DPC例程以下:

DriverEntry中初始化代碼以下:

R3中代碼以下:

藍屏:

與0x000000D1藍屏略有不一樣:

其實這個問題的出現是一個失誤。就是我沒有在DriverEntry中註冊派遣函數,仍舊用的上一例中的派遣函數而忘記初始化Timer了,因此形成了這個藍屏。

輸出結果看:

發現DPC例程所屬的線程是不斷變化的,這驗證了那句話「該線程能夠運行在任一線程上下文」。

  • IRP超時處理

不少時候,IRP被傳送到底層驅動程序後,因爲硬件設備的問題,IRP不能獲得及時的處理,甚至有可能永遠都不會被處理。這時候須要對IRP超時狀況作出處理,一旦在規定時間內IRP沒有被處理,操做系統會進入到IRP的超時處理函數中。

首先初始一個定時器對象和DPC對象,並將DPC例程和定時器對象進行關聯。在每次對IRP操做前,開啓定時器,並設置好必定的超時。若是在指定時間內對IRP的處理沒有結束,那麼操做系統就進入DPC例程。

示例代碼:

派遣函數中的代碼:

DPC例程代碼:

不要忘記在DriverEntry中初始化計時器對象和DPC例程對象:

運行輸出結果:

相關文章
相關標籤/搜索