内核模式驱动程序框架 (KMDF)处理I/O请求的情况

内核模式驱动程序框架 (KMDF)处理I/O请求的情况


内核模式驱动程序框架 (KMDF)处理I/O请求的情况

 

在内核模式驱动程序框架 (KMDF) 中,一个 WDFREQUEST 对象代表一个 I/O 请求。每个 WDFREQUEST对象都与一个或多个 WDFMEMORY 对象关联,每个这种 WDFMEMORY对象都代表一个用于请求中的输入或输出的缓冲区。

 

KMDF创建 WDFREQUEST 和 WDFMEMORY 对象来表示一个传入的 I/O 请求时,它将 WDFREQUEST 对象设置为关联的WDFMEMORY 对象的父对象。因此,WDFMEMORY 对象可以持续的时间不会比 WDFREQUEST 对象的生存期长。当KMDF 驱动程序完成 I/O 请求时,框架删除 WDFREQUEST 对象和 WDFMEMORY对象,因此这两个对象的句柄变得无效。

 

但是,底层的缓冲区不同。根据哪个组件创建了缓冲区以及如何创建缓冲区,缓冲区可能有一个引用计数并且可能“属于”WDFMEMORY对象(或者可能不属于)。如果 WDFMEMORY 对象拥有缓冲区,那么缓冲区拥有一个引用计数并且其生存期被限制为 WDFMEMORY对象的生存期。如果一些其他组件创建了缓冲区,那么缓冲区的生存期和 WDFMEMORY对象不相关。

 

KMDF驱动程序也可以创建自己的 WDFREQUEST 对象来发送到 I/O 目标。驱动程序创建的请求可以重用驱动程序在一个 I/O请求中接收到的现有 WDFMEMORY 对象。频繁发送请求到 I/O 目标的驱动程序可以重用其创建的 WDFREQUEST对象。但是,它不能重用从 KMDF 接收到的 WDFREQUEST 对象。

 

理解 WDFREQUEST 对象、WDFMEMORY对象和底层缓冲区的生存期非常重要。这可以确保您的驱动程序不会试图引用无效的句柄或缓冲区指针。

 

考虑可能的使用场景:

 

  场景 1:驱动程序从 KMDF 接收到一个 I/O请求、处理它并完成它。

 

  场景 2:驱动程序从 KMDF 接收到一个 I/O请求并将其转发给一个 I/O 目标。

 

  场景 3:驱动程序发出一个使用新 WDFMEMORY对象的 I/O 请求。

 

  场景 4:驱动程序发出一个使用现有 WDFMEMORY对象的 I/O 请求。

 

  场景 5:驱动程序重用其创建的 WDFREQUEST对象。

 

 

场景 1:驱动程序从 KMDF 接收到一个 I/O请求、处理它并完成它

在最简单的场景中,KMDF 将请求分发给驱动程序,驱动程序执行 I/O并完成请求。在这种情况中,底层缓冲区可能已经被用户模式应用程序、另一个驱动程序或操作系统本身创建。如果驱动程序执行带缓冲的 I/O或直接 I/O,那么它可以通过下列方法之一访问缓冲区:

 

1、 通过调用 WdfRequestRetrieveInputMemory 或WdfRequestRetrieveOutputMemory(取决于这是写请求还是读请求)来获取与 WDFREQUEST 关联的WDFMEMORY 对象的句柄,然后通过调用 WdfMemoryGetBuffer 来获取缓冲区的指针。要读写缓冲区,驱动程序调用WdfMemoryCopyFromBuffer 或WdfMemoryCopyToBuffer。

 

2、通过调用WdfRequestRetrieveInputBuffer 或WdfRequestRetrieveOutputBuffer(取决于这是读请求还是写请求)来获取缓冲区的指针。

 

 

当驱动程序调用 WdfRequestComplete 来完成 I/O 请求时,框架删除 WDFMEMORY对象。然后缓冲区指针无效。

 

场景 2:驱动程序从 KMDF 接收到一个 I/O 请求并将其转发给一个 I/O目标

如果驱动程序将请求转发给一个 I/O 目标而不自己执行I/O,那么会发生什么?在这种情况中,驱动程序将进行如下处理:

 

1.调用WdfDeviceGetIoTarget 来获取一个 I/O目标对象的句柄。

 

2.调用WdfRequestFormatUsingCurrentType 来为 I/O 目标格式化请求,或者调用WdfRequestRetrieveXxxMemory 来获取内存对象和调用WdfIoTargetFormatRequestForXxx 来为 I/O目标格式化请求。

 

3.调用WdfRequestSend 将请求发送到 I/O目标。

 

 

驱动程序不得以任何方式改动 WDFREQUEST 对象或 WDFMEMORY 对象。如果驱动程序为请求设置一个 I/O 完成回调,那么KMDF 在 I/O 目标完成请求之后调用该回调。缓冲区指针保持有效,直到驱动程序(不是 I/O 目标)调用WdfRequestComplete。

 

下面的示例代码显示驱动程序如何从传入的 WDFREQUEST 对象检索一个 WDFMEMORY对象的句柄,格式化请求要发送到 I/O目标的请求,然后发送请求:

 

VOIDEvtIoRead( IN WDFQUEUE Queue, IN WDFREQUEST Request, IN size_tLength ) { NTSTATUS status; WDFMEMORY memory; WDFIOTARGET ioTarget;BOOLEAN ret; ioTarget =WdfDeviceGetIoTarget(WdfIoQueueGetDevice(Queue));

 

status =WdfRequestRetrieveOutputMemory(Request, &memory); if(!NT_SUCCESS(status)) { goto End; }

 

status =WdfIoTargetFormatRequestForRead(ioTarget, Request, memory, NULL,NULL); if (!NT_SUCCESS(status)) { goto End;}

 

WdfRequestSetCompletionRoutine(Request,RequestCompletionRoutine, WDF_NO_CONTEXT);

 

ret =WdfRequestSend (Request, ioTarget, WDF_NO_SEND_OPTIONS); if (!ret){ status = WdfRequestGetStatus (Request); goto End;}

 

return;

 

End:WdfRequestComplete(Request, status);return;

 

}

I/O目标完成请求时,KMDF调用驱动程序为请求设置的完成回调。下面是这个例程的代码:

 

VOIDRequestCompletionRoutine( IN WDFREQUEST                 Request, IN WDFIOTARGET                Target,PWDF_REQUEST_COMPLETION_PARAMS CompletionParams, INWDFCONTEXT                 Context ) { UNREFERENCED_PARAMETER(Target);UNREFERENCED_PARAMETER(Context);

 

WdfRequestComplete(Request,CompletionParams->IoStatus.Status);

 

return;

 

}

当驱动程序在其完成回调中调用 WdfCompleteRequest 时,KMDF 销毁 WDFMEMORY 对象。驱动程序在 EvtIoRead函数中检索到的 WDFMEMORY 对象句柄现在无效。

 

场景 3:驱动程序发出一个使用现有 WDFMEMORY对象的请求

一些驱动程序发出它们自己的 I/O 请求并将它们发送到 I/O 目标(由 WDFIOTARGET 对象表示)。这种请求必须使用驱动程序创建的WDFREQUEST 对象;驱动程序不能重用从 KMDF 接收到的 WDFREQUEST 对象。但是,驱动程序可以从传入的WDFREQUEST 中获取一个 WDFMEMORY 对象,然后创建一个使用该 WDFMEMORY对象的新请求。驱动程序不得更改底层的缓冲区,但是它可以在格式化新的 I/O 请求时传递一个缓冲区偏移。要格式化一个使用现有WDFMEMORY 对象的新 I/O 请求,驱动程序调用 WdfIoTargetFormatRequestXxx 或WdfIoTargetSendXxxSynchronously 方法之一。如果驱动程序异步发送请求,那么它应该注册一个 I/O完成回调例程,从而使 KMDF 可以在请求完成时通知它。

 

KMDF格式化发送到 I/O 目标的请求时,它代表 I/O 目标对象清除回收的 WDFMEMORY 对象上的一个引用。I/O目标对象保留这个引用,直到发生下列情况之一:

 

1、 请求已经完成。

 

2、 驱动程序通过调用 WdfIoTargetFormatRequestXxx 或WdfIoTargetSendXxxSynchronously 方法之一重新格式化 WDFREQUEST对象。

 

3、 驱动程序调用WdfRequestReuse。

 

 

当新的 I/O 请求完成时,WDF 调用驱动程序为此请求设置的 I/O 完成回调。这时候,WDFIOTARGET对象仍然持有 WDFMEMORY 对象上的一个引用。因此,在 I/O 完成回调中,驱动程序必须在完成原始请求(它从其中获取WDFMEMORY 对象)之前在驱动程序创建的 WDFREQUEST 对象上调用 WdfRequestReuse。如果驱动程序不调用WdfRequestReuse,那么会由于额外的引用而发生错误检查。

 

场景 4:驱动程序发出一个使用新 WDFMEMORY对象的请求

根据底层缓冲区的来源,KMDF 为驱动程序提供三种方式来创建新的 WDFMEMORY对象。

 

1、 为了让 KMDF 分配缓冲区,驱动程序调用WdfMemoryCreate。根据驱动程序的规范,可以从分页池或未分页池分配缓冲区。

 

2、 要从前面定义的后备链表分配缓冲区,驱动程序调用WdfMemoryCreateFromLookaside。

 

3、 要将先前分配的缓冲区指定给一个新的 WDFMEMORY 对象,驱动程序调用WdfMemoryCreatePreallocated。驱动程序可以在以后通过调用 WdfMemoryAssignBuffer将不同的缓冲区指定给相同的 WDFMEMORY 对象。

 

 

如果缓冲区由 KMDF 分配或从驱动程序创建的 WDFLOOKASIDE 列表分配,那么 WDFMEMORY对象拥有缓冲区,因此只要 WDFMEMORY 对象存在,缓冲区指针就会保持有效。发出异步 I/O 请求的驱动程序应该始终使用WDFMEMORY 对象拥有的缓冲区,从而 KMDF 可以确保缓冲区持续存在,直到 I/O请求已经完成并返回到发出请求的驱动程序。因此,驱动程序应该使用 WdfMemoryCreate 或WdfMemoryCreateFromLookaside 为异步 I/O 请求来创建 WDFMEMORY对象。

 

如果驱动程序通过调用 WdfMemoryCreatePreallocated 将先前分配的缓冲区指定给新的 WDFMEMORY对象,那么 WDFMEMORY 对象不会拥有该缓冲区。在这种情况中,WDFMEMORY对象的生存期和底层缓冲区的生存期无关。驱动程序必须管理缓冲区的生存期,并且不得试图使用一个无效的缓冲区指针。

 

驱动程序不能对在 I/O 请求中接收到的 WDFMEMORY 对象使用WdfMemoryAssignBuffer;它只能对驱动程序创建的新 WDFMEMORY对象使用这种方法。

 

 

场景 5:驱动程序重用其创建的 WDFREQUEST对象

驱动程序可以重用其创建的 WDFREQUEST 对象,但是它必须在每次重用之前通过调用 WdfRequestReuse来重新初始化每个这种对象。关于重新初始化一个 WDFREQUEST 对象的示例代码,请参见 KMDF 发布版提供的 Toastmon和 NdisEdge 例子。

 

您应该做什么?

 

1、 不要试图在完成关联的 WDFREQUEST 对象之后引用 WDFMEMORY对象下层的缓冲区。

 

2、 当发送异步 I/O 请求时,始终使用由 WdfMemoryCreate 或WdfMemoryCreateFromLookaside 分配的缓冲区。

 

3、 如果您的驱动程序发送一个包含重用的 WDFMEMORY 对象的 I/O请求,那么它必须始终在新请求完成之后但在完成从其中检索 WDFMEMORY 对象的请求之前调用WdfRequestReuse。

 

 

你可能感兴趣的:(内核模式驱动程序框架 (KMDF)处理I/O请求的情况)