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例程对象:
运行输出结果: