《Windows驱动开发技术详解》之自定义StartIO

  • 自定义StartIO

系统定义的StartIO队列只能使用一个队列(DDK提供的StartIO内部只有一个队列),这个队列将所有的IRP进行处理化。例如,读、写操作都会混在一起进行串行处理。然而,有时需要读、写分别进行串行化处理。这就需要自定义StartIO例程。当然,程序员需要自己去维护这个IRP队列。

当使用StartIO时,程序员不用关心队列的“入队”和“出队”操作,这些都由操作系统自动负责。但是如果使用自定义的StartIO,程序员需要自己负责“入队”和“出队”操作。

虽说队列的个数是由程序员自己定义的,但是队列的数据结构以及队列中元素的数据结构还要使用DDK提供的结构。其中,存储队列的结构如下:

 1 typedef struct _KDEVICE_QUEUE {
 2     CSHORT Type;
 3     CSHORT Size;
 4     LIST_ENTRY DeviceListHead;
 5     KSPIN_LOCK Lock;
 6 
 7 #if defined(_AMD64_)
 8 
 9     union {
10         BOOLEAN Busy;
11         struct {
12             LONG64 Reserved : 8;
13             LONG64 Hint : 56;
14         };
15     };
16 
17 #else
18 
19     BOOLEAN Busy;
20 
21 #endif
22 
23 } KDEVICE_QUEUE, *PKDEVICE_QUEUE, *PRKDEVICE_QUEUE;

存储队列中每个元素的结构如下:

1 typedef struct _KDEVICE_QUEUE_ENTRY {
2     LIST_ENTRY DeviceListEntry;
3     ULONG SortKey;
4     BOOLEAN Inserted;
5 } KDEVICE_QUEUE_ENTRY, *PKDEVICE_QUEUE_ENTRY, *PRKDEVICE_QUEUE_ENTRY;

如果要插如一个IRP进队列或者从队列移除一个IRP,使用内核函数:KeInsertDeviceQueue和KeRemoveDeviceQueue。

这里值得注意的是KeInsertDeviceQueue的返回值。如果设备不忙,则可以直接处理IRP,因此这时候不需要插入队列,返回值为FALSE。如果设备正在处理,这时候需要将IRP插入队列,这时候会返回TRUE

示例代码如下:

派遣函数代码如下:

 1 NTSTATUS HelloDDKDispatchRead_MyStart(PDEVICE_OBJECT pDevObj, PIRP pIrp){
 2     DbgPrint("Enter HelloDDKDispatchRead_MyStart!\n");
 3     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
 4         pDevObj->DeviceExtension;
 5     IoMarkIrpPending(pIrp);
 6     KIRQL oldirql;
 7     KeRaiseIrql(DISPATCH_LEVEL, &oldirql);
 8     if (!KeInsertDeviceQueue(&pDevExt->device_queue_read, &pIrp->Tail.Overlay.DeviceQueueEntry))
 9         MyStartIO(pDevObj, pIrp);
10     KeLowerIrql(oldirql);
11     DbgPrint("Leave HelloDDKDispatchRead_MyStart!\n");
12     return STATUS_PENDING;
13 }

自定义StartIO代码如下:

 1 VOID MyStartIO(PDEVICE_OBJECT DeviceObject, PIRP pFirstIrp){
 2     DbgPrint("Enter MyStartIO!\n");
 3     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
 4         DeviceObject->DeviceExtension;
 5     PKDEVICE_QUEUE_ENTRY device_entry;
 6     PIRP pIrp = pFirstIrp;
 7     do{
 8         KEVENT event;
 9         KeInitializeEvent(&event, NotificationEvent, FALSE);
10         LARGE_INTEGER timeout;
11         timeout.QuadPart = -3 * 1000 * 1000 * 10;
12         KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);
13 
14         pIrp->IoStatus.Status = STATUS_SUCCESS;
15         pIrp->IoStatus.Information = 0;
16         IoCompleteRequest(pIrp, IO_NO_INCREMENT);
17         device_entry = KeRemoveDeviceQueue(&pDevExt->device_queue_read);
18         /*if (NULL == device_entry){
19             break;
20         }*/
21         pIrp = CONTAINING_RECORD(device_entry, IRP, Tail.Overlay.DeviceQueueEntry);
22     } while (NULL != device_entry);
23     DbgPrint("Leave MyStartIO!\n");
24 }

DriverEntry中的队列初始化代码如下:

InitializeListHead(&pDevExt->device_queue_read.DeviceListHead);

 第一次运行时,R3层代码如下:

CreateFile是异步,

输出结果如下:

我们看到每次都进入了MyStartIO。这里尽管是Pending住每个IRP,但是由于是异步完成,所以每个ReadFile都会先返回,然后将它的IRP留给底层去处理。此外,第一次ReadFile时,队列为空,这时KeInsertDeviceQueue返回FALSE,进入MyStartIO,MyStartIO结束掉这个挂起的IRP,然后才又进入下一个ReadFile。

如果把R3的代码改为并发:

输出结果为:

发现根本没有进入MyStartIO。因为并发,速度很快,一口气将十个IRP发给队列,所以队列很忙,没有进入MyStartIO。当然此时是异步完成,所以即使返回Pending也没有卡在原地。

问题:

第一个R3测试里,是先在MyStartIO中结束掉了这个IRP,而又返回STATUS_PENDING,那么是返回SUCCESS还是PENDING?

实验证明,这样可以成功结束掉IRP 即便返回Pending,也不会挂起。后来我问了下老师为什么,老师说,IoStatus.Status的设置的值才是返回给R3的值,而下边的return返回的值是返回给系统框架的这是显而易见的,因为派遣函数的调用是从应用层一层一层向下调用的,所以这个派遣函数的返回值定是要返回给调用它的那个函数,也就是系统框架中的一个函数。而真正要返回给R3的值,当然要保存在一个结构体里

如果底层代码改为:

因为没有结束掉IRP所以迟迟没有返回:

我们再排除下是否是CreateFile异步完成的原因。改成同步完成:

R0层仍旧是:

从结果上看:

显然是没有pending而是成功返回了。

第二个R3测试里,全部返回STATUS_PENDING,没有去结束掉这些IRP,会有什么影响?

只要没有WaitForMultiObject,那么这个程序就可以顺利运行到最后。因为是异步完成,所以派遣函数会先返回,上层运行上层的,底层去自己继续处理IRP。

  • 注意1:

一开始我尝试自己去初始化设备扩展中的成员:

这样初始化会报错:

我就尝试把这个KDEVICE_QUEUE中的数据摘出来自己单独组织成一个结构体:

此时再去初始化这样的一个结构体就不会报错:

这说明DDK可能对系统提供的结构体有一种“保护”,它会防止你自己胡乱初始化。

  • 注意2:

一开始我注册了一个这个ReadFile的派遣函数:

运行之后蓝屏:

 

仔细想想这里哪错了,很简单,没有在DriverEntry中初始化你操作的这个队列。

可以参考:http://www.yiiyee.cn/Blog/bsod-0x3b-1/

https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff558949(v=vs.85).aspx

 

posted @ 2016-06-09 20:22  _No.47  阅读(447)  评论(0编辑  收藏  举报