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定時器內部使用定時器對象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的超時處理函數中。
首先初始一個定時器對象和DPC對象,並將DPC例程和定時器對象進行關聯。在每次對IRP操做前,開啓定時器,並設置好必定的超時。若是在指定時間內對IRP的處理沒有結束,那麼操做系統就進入DPC例程。
示例代碼:
派遣函數中的代碼:
DPC例程代碼:
不要忘記在DriverEntry中初始化計時器對象和DPC例程對象:
運行輸出結果: