《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。

示例代码:

现在DriverEntry中初始化计时器:

再写相应的派遣函数:

 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例程:

应用层代码如下:

发送相应控制码到底层,每三秒输出一个“Time Out”:

这个例子忘记停止计时器,则会一直输出下去。

  • 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例程对象:

运行输出结果:

posted @ 2016-06-13 11:17  _No.47  阅读(1676)  评论(0编辑  收藏  举报