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