技术的乐趣在于分享,欢迎多多交流,多多沟通。
全部博文(877)
分类: Windows平台
2015-05-27 11:23:52
Canceling I/O Requests
正在被设备处理的IO请求,可以被应用程序,系统,或者驱动取消。如果设备的IO操作被取消,IO管理器尝试取消所有的和IO操作所关联的没有被处理的IO请求。设备驱动可以一个例程得到通知但IO管理器尝试取消IO请求的时候,然后驱动可以通过设置IO请求的完成状态值为ERROR_OPERATION_ABORTED取消请求。
作为基于框架的驱动,框架会处理大多数的请求取消工作。如果设备的IO操作被取消了,框架以HRESULT_FROM_WIN32(ERROR_OPERATION_ABORTED)的完成状态来结束请求。下面是一些和取消操作所关联的IO请求。
框架已经放入在驱动默认的IO队列中的请求,但是这个请求还未分发。
驱动因为调用IWDFIoQueue::ConfigureRequestDispatching,框架将请求转发到另一个队列,但是请求还未分发。
因为框架要取消这些请求,框架不会将这些请求分发到驱动。
如果,框架已经将IO请求分发到驱动,驱动拥有请求,所以框架不能取消请求。在这种情况下,驱动可以取消IO请求,但是框架必须通知驱动这个请求必须取消。驱动可以在其提供的IRequestCallbackCancel::OnCancel回调函数中得到这个通知。
通常驱动从IO队列中接收请求,但是,有时候驱动并不处理这个请求,而是将请求重新入到同样一个队列或另一个队列延后处理。举例来说,框架可能将请求发送给驱动任一个请求处理例程,驱动可能在其例程中调用IWDFIoRequest::ForwardToIoQueue将请求发送到不同的队列,或者调用IWDFIoRequest2::Requeue 将请求重排在这个队列中。
在一些情况下,框架可以取消已经在IO队列中的IO请求。当请求正在被取消,如果驱动已经为请求已经在的IO队列注册了一个回调函数,框架会调用回调函数,而不是取消请求。如果框架调用驱动的回调函数,驱动必须取消请求。
通常,当请求被取消,框架也会取消和IO请求所关联的所有还没有分发到驱动的请求。如果驱动接收到一个请求并将其重新排队,框架会取消请求除非驱动为IO队列已经提供了回调函数。
Calling MarkCancelable
驱动可以调用IWDFIoRequest::MarkCancelable注册一个 IRequestCallbackCancel::OnCancel 的回调函数。如果驱动已经调用MarkCancelable,但是和IO操作相关联的请求已经被取消了,框架调用驱动的OnCancel回调函数,让驱动取消IO请求。
驱动应该调用MarkCancelable,如果驱动会拥有请求比较长的时间。举例来说,驱动可能不得不等待设备响应,或者当驱动接收到一个请求,但是其创建一组请求,并发送到下层驱动,并等待下层驱动进行处理。
Calling IsCanceled
如果驱动没有调用MarkCancelable,或者在调用后,又调用了IWDFIoRequest::UnmarkCancelable。驱动将不知道取消操作因此它将预前定义的处理请求。
Calling IsCanceled
如果驱动没有调用MarkCancelable去注册一个OnCancel回调函数,但是它可以调用IWDFIoRequest2::IsCanceled 去发现是否IO管理器已经试图取消IO请求。如果IsCanceled返回TRUE,驱动应该取消请求。
举例来说,驱动接收到一个很大的读写请求,并将其分段成很多小的请求,如果驱动没有调用MarkCancelable去注册取消通知,当驱动的IO目标对象完成每一个小的请求的时候,驱动可以调用IsCanceled是否请求被取消。
Canceling the Request
取消请求可能涉及到如下操作。
停止正在处理的IO操作。
停止分发请求到IO目标对象。
调用IWDFIoRequest::CancelSentRequest尝试取消已经预前已经发送到IO目标对象的请求。
如果驱动正在为从框架接收到的IO请求对象取消其IO操作,驱动必须调用IWDFIoRequest::Complete or IWDFIoRequest::CompleteWithInformation,并将CompletionStatus设置为 HRESULT_FROM_WIN32(ERROR_OPERATION_ABORTED来结束请求.(如果驱动已经调用IWDFDevice::CreateRequest创建了一个请求对象,驱动调用IWDFObject::DeleteWdfObject 删除请求对象,而不是结束请求)
Completing I/O Requests
每一个IO请求都必须最终由UMDF驱动完成。为了完成请求,驱动必须调用IWDFIoRequest::Complete or IWDFIoRequest::CompleteWithInformation方法。当驱动结束了请求,一般有如下几种状况:
IO请求操作成功完成。
IO请求操作以失败完成。
IO请求操作不支持或者当它接收到的时候不是一个正确的时候,因为驱动不能和设备进行联系。
IO请求被取消。
驱动调用 IWDFIoRequest::CompleteWithInformation 方法,并为请求操作传递额外的信息。举例来说,对于读操作,驱动应该提供读了多少字节。
为了完成IO请求,在调用IWDFIoRequest::Complete or IWDFIoRequest::CompleteWithInformation中驱动应该传递合适的完成状态给CompletionStatus参数。驱动利用HRESULT代码建立完成请求的状态码。
在将其传递完成请求的状态值给反射器(Wudlfrd.sys)之前,它会将HRESULT代码转换为NTSTATUS代码。反射器传递NTSTATUS代码给操作系统。操作系统在将返回值返回给发出请求的应用程序之前会转换NTSTATUS代码为WIN32错误码。
为了确保你的驱动错误代码可以被正常转换,你应该用如下的方法来创建错误码。
使用Winerror.h中定义的错误码,适应HRESULT_FROM_WIN32宏。
使用Ntstatus.h中定义的错误码,适应HRESULT_FROM_NT宏。
VOID STDMETHODCALLTYPE
CMyQueue::OnWrite(
__in IWDFIoQueue *pWdfQueue,
__in IWDFIoRequest *pWdfRequest,
__in SIZE_T BytesToWrite
)
{
--------------------
if( BytesToWrite > MAX_WRITE_LENGTH ) {
pWdfRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_MORE_DATA), 0);
return;
}
---------------------
}
当驱动以成功结束请求,它应该返回S_OK,它是一个HRESULT值,因为S_OK等价于Winerror.h中的NO_ERROR,Ntstatus.h中的STATUS_SUCCESS,转换宏不需要使用。
如果Driver Verifier对于反射器使能,如果它发现无效的状态码,将导致系统CRASH产生。
驱动预前发送一个请求到下层驱动,当下层驱动结束请求,需要得到通知。驱动可以调用 IWDFIoRequest::SetCompletionCallback 方法来注册这个通知。下层驱动结束这个请求的时候,框架会调用这个回调函数。在 IRequestCallbackRequestCompletion::OnCompletion 完成例程中执行一些结束请求需要的操作。
驱动如果之前调用IWDFDevice::CreateRequest创建请求,驱动就不要结束请求。而是,驱动在IO目标完成这个请求的时候,调用IWDFObject::DeleteWdfObject 删除请求对象。
举例来说,驱动可能收到一个超过其IO目标对象一次处理的数据读写请求,驱动必须将其进行分段,发送这些小的请求到一个或多个IO目标对象,遵照如下的方法。
调用IWDFDevice::CreateRequest为小段的请求创建额外的一个请求对象。
驱动可以同步发送请求到IO目标对象。小段请求的完成例程IRequestCallbackRequestCompletion::OnCompletion 回调函数可以调用 IWDFIoRequest2::Reuse 重用请求,再次将其发送到IO目标对象。当IO目标对象结束了最后一个小段的请求对象,驱动在其OnCompletion完成例程中调用IWDFObject::DeleteWdfObject 删除其创建的小段的请求对象,然后驱动调用IWDFIoRequest::Complete结束原始的请求。
调用IWDFDevice::CreateRequest为小段的请求创建额外的多个请求对象。
驱动的IO目标对象可以异步的处理这些额外的请求对象。驱动可以为每一个小段的请求创建一个OnCompletion例程。每次OnCompletion例程被调用,它可以调用IWDFObject::DeleteWdfObject 删除其创建的小段请求对象.当IO目标对象完成所有的小段请求,驱动可以调用IWDFIoRequest::Complete去结束原始请求。
Obtaining Completion Information
为了获得另外一个驱动已经完成的IO请求的信息,UMDF驱动可以:
利用 IWDFRequestCompletionParams接口得到IO请求的完成状态和其他的信息。
利用 IWDFIoRequestCompletionParams 接口得到IO请求的内存区。
利用 IWDFUsbRequestCompletionParams 接口可以得到发送给USB目标管道对象的内存和其他和请求相关联的其它信息。
另外,UMDF驱动可以使用IWDFIoRequest2::GetStatus方法得到IO请求的当前状态,不管是请求结束前还是结束后。
Creating UMDF HID Minidrivers
开始于UMDF1.11,还有后面的2.0,你可以HID设备创建一个UMDF驱动。这样的驱动叫做HID小端口驱动。系统提供的HID类驱动(HidClass.sys)和框架提供了一些冲突的派遣例程为小端口驱动来处理一些IO请求(比如PNP和POWER请求)。其结果是,你的HID小端口驱动不能同时链接类驱动和框架。因此,微软提供了另外一个WDF驱动,
MsHidUmdf.sys,做为HidClass.sys的小端口驱动进行匹配。
功能设备对象(FDO)和MsHidUmdf.sys相关联,HidClass.sys作为MsHidUmdf.sys的帮助模块。反射器(WudfRd.sys)在这种请况下作为下层的过滤驱动。
系统提供的MsHidUmdf.sys驱动可以被供应商提供的HidUmdf.sys替换。
MsHidUmdf.sys 驱动被包含在Windows8,以及以后的系统中。
MsHidUmdf.sys驱动调用HID类驱动的HidRegisterMinidriver例程注册作为实际的小端口驱动。虽然,MsHidUmdf.sys是设备的功能驱动,但是在它更新了IRP空间位置满足UMDF的需求后,会将类驱动的IO请求传递给你的驱动。你提供的基于UMDF的HID小端口驱动实际上是MsHidUmdf.sys驱动的下层过滤驱动。驱动的IDriver::OnDeviceAdd回调函数必须调用IWDFDeviceInitialize::SetFilter.
反射器(WudfRd.sys)然后会打断MsHidUmdf.sys和你的UMDF驱动之间的联系。
你的驱动可以创建IO队列来接收MsHidUmdf.sys从类驱动接到的发送给你的驱动的请求。当驱动的设备IO控制请求OK后,你的驱动可以调用 IQueueCallbackDeviceIoControl 例程得到通知。你的驱动IQueueCallbackDeviceIoControl::OnDeviceIoControl 方法来分别处理那些特定的IOCTL方法。
IOCTL_HID_ACTIVATE_DEVICE
IOCTL_HID_DEACTIVATE_DEVICE
IOCTL_HID_GET_DEVICE_ATTRIBUTES
IOCTL_HID_GET_DEVICE_DESCRIPTOR
IOCTL_HID_GET_INDEXED_STRING
IOCTL_HID_GET_REPORT_DESCRIPTOR
IOCTL_HID_GET_STRING
IOCTL_HID_READ_REPORT
IOCTL_HID_WRITE_REPORT
IOCTL_UMDF_GET_PHYSICAL_DESCRIPTOR
IOCTL_UMDF_HID_GET_FEATURE
IOCTL_UMDF_HID_GET_INPUT_REPORT
IOCTL_UMDF_HID_SET_FEATURE
IOCTL_UMDF_HID_SET_OUTPUT_REPORT
对于Windows8以后的操作系统,你可以使用MsHidUmdf.sys驱动。如果你要为之前的Windows系统提供一个UMDF HID 小端口驱动,你必须在你的驱动包中包含HidUmdf.sys和UMDF1.11.HidUmdf.sys例子代码包含在WINDOWS 8的WDK中。
基于UMDF的HID小端口驱动必须设置如下的INF节。
UmdfKernelModeClientPolicy
这个节必须设置为AllowKernelModeClients,让内核传递驱动可以被堆栈装载。
[hidumdf.NT.Wdf]
UmdfKernelModeClientPolicy = AllowKernelModeClients
UmdfMethodNeitherAction
这个节必须设置为Copy,允许UMDF处理NETHOD_NEITHER类型的IOCTL。
[hidumdf.NT.Wdf]
UmdfMethodNeitherAction=Copy
UmdfFileObjectPolicy
这个节必须设置为 AllowNullAndUnknownFileObjects 允许UMDF处理和那些NULL或者不知道的文件对象(文件对象之前驱动没有见过的创建请求)相关联的请求。
[hidumdf.NT.Wdf]
UmdfFileObjectPolicy=AllowNullAndUnknownFileObjects
UmdfFsContextUsePolicy
这个节必须设置为CanUseFsContext2,允许框架在WDM文件对象的FsContext2成员装入一些信息.
[hidumdf.NT.Wdf]
UmdfFsContextUsePolicy = CanUseFsContext2
Accessing Data Buffers in UMDF 1.x Drivers
当驱动接收到一个读,写或者设备IO控制请求,请求对象包含一个input空间或ouput空间,或者两个都有。
输入空间包含驱动需要的信息。对于写请求,包含设备功能驱动必须发送给设备的数据。对于设备IO控制请求,输入空间包含驱动必须处理的一些操作的类型信息。
输出空间从驱动接收信息。对于读操作,代表从驱动从设备读到的信息。对于设备IO控制请求,输出空间包含一些请求指定的IO控制码的状态或者其他的信息。
你驱动使用的进入请求空间的方法,取决于设备的数据空间的访问方式。UMDF支持如下的空间访问方法。
UMDF1.9之前的版本,仅仅只支持缓冲区IO的访问方式。在这种版本中,UMDF仅仅只能使用缓冲区IO来对数据进行读写,设备IO控制请求。访问IO请求的数据空间,UMDF驱动可以调用IWDFIoRequest::GetInputMemory and IWDFIoRequest::GetOutputMemory 方法。
从UMDF1.9版本开始,缓冲区IO和直接IO,被基于UMDF驱动说支持。UMDF驱动使用的是版本1.9以后的,应该使用IWDFIoRequest2::RetrieveInputBuffer, IWDFIoRequest2::RetrieveInputMemory, IWDFIoRequest2::RetrieveOutputBuffer, or IWDFIoRequest2::RetrieveOutputMemory来访问数据空间。
对于第三种方法,即不是缓冲区IO也不是直接IO.基于UMDF的驱动不支持这种方式.但是UMDF可以转换这些IO请求从"neither"到UMDF版本支持的方法.
在大多数的情况下,基于UMDF驱动对于缓冲区IO和直接IO调用同样的UMDF对象方法来访问数据空间,直接IO比缓冲区IO有更好的效率.
下面解释了原因:
Specifying a Preferred Buffer Access Method
基于UMDF框架的驱动,可以在调用IWDFDriver::CreateDevice创建设备对象前,调用IWDFDeviceInitialize2::SetIoTypePreference 设置IO的访问方式。
一般来说,驱动可以为读写请求指定一种访问类型,而为设备IO控制请求指定另外一种类型。
对于设备IO控制请求:
对于UMDF1.9版本之前,UMDF对于所有的IO控制请求都使用缓冲区IO.
开始于UMDF1.9以后, 也可以使用IWDFDeviceInitialize2::SetIoTypePreference声明设备控制IO是使用缓冲区IO还是直接IO.
Specifying a Buffer Retrieval Mode
在UMDF1.9之前,当UMDF接收到IO请求时候,它立刻将请求的空间给驱动使用(将空间拷贝到UMDF驱动的主持进程),这种方式叫做立即恢复。如果错误产生,UMDF使用错误的返回值结束IO请求,并不把IO请求分发给驱动。
从UMDF1.9开始,支持两种模式,一种是立即模式,一种是延迟模式。延迟模式直到驱动要访问请求的空间时才将IO请求空间的内容拷贝到驱动主持进程。如果错误发生,空间访问函数返回错误的值给驱动。
你的驱动可以遵照如下的条例调用 IWDFDeviceInitialize2::SetIoTypePreference 设置模式。
如果你的驱动指定直接IO的访问方式,也必须指定延迟恢复模式。直接IO方式只工作在延迟恢复。
对于UMDF1.9以后的驱动,对于所有的IO请求都应该指定延迟恢复模式,无论驱动是使用缓冲区IO还是直接IO。
如果你的驱动不指定空间恢复模式,UMDF使用立即恢复模式。
驱动堆栈中的所有UMDF驱动都必须同样的恢复模式。如果有的驱动指定立即恢复模式,而有的使用延迟恢复模式,UMDF将使用立即恢复模式。
How UMDF Chooses a Buffer Access Method for an I/O Request
驱动堆栈中的所有基于UMDF的驱动必须使用同样的IO访问方式访问设备空间。如果UMDF发现有些驱动使用缓冲区IO,而另一些驱动使用直接IO,UMDF对于所有的驱动选择使用缓冲区IO。如果有些驱动只能使用直接IO,UMDF在建立驱动堆栈时上报一个系统事件错误。
驱动可以通过调用IWDFDevice2::GetDeviceStackIoTypePreference去查询UMDF分发给设备的读写和设备控制请求的IO访问方式。
在一些情况下,驱动预前调用IWDFDeviceInitialize2::SetIoTypePreference指定了直接IO的访问方式,但是对于更好的效率,UMDF还是会选择缓冲区IO的访问方式,
这是因为,UMDF使用的缓冲区IO比直接IO在小空间的情况下,拷贝数据空间更加快。
你可以设置一个REG_DWORD类型的DirectTransferThreshold注册表值,框架使用它作为使用直接IO的最小空间长度。一般来说,你不需要提供这个值为了最好的系统效率。
这个注册表值在硬件子键的Device Parameters\WUDF 下面。
框架使用如下的约定去探测你提供的DirectTransferThreshold的值。这个值的大小假设设置从一页的大小4096,这对安腾处理器不适用。
如果你设置的DirectTransferThreshold的值小于8192,框架将使用8192,框架对于小于8192byte的空间使用缓冲区IO,对于大于8192的空间使用直接IO.
如果你设置DirectTransferThreshold比8192大,框架将其增大至下一个PAGE_SIZE的的大小。
UMDF使用直接IO,仅仅是在页对齐的条件下,比如页面的开始或者结束的位置。如果没有使用页面对齐的数据,UMDF使用缓冲区IO.换句话说,UMDF对于很多IO请求的大数据传输可以使用缓冲区IO和直接IO.
对于设备IO控制请求,UMDF使用直接IO,仅仅在IOCTL中指定了直接IO,设备驱动也已经调用IWDFDeviceInitialize2::SetIoTypePreference 指定了直接IO.
驱动使用同一组请求对象方法访问数据空间,不论空间访问方法。因此,大多数驱动不知道UMDF是使用的是缓冲区IO还是直接IO.
How a Driver Can Obtain the Access Method for an I/O Request
在一些情况下,如果知道访问数据的方法可以提高设备和驱动的效率。在这种情况下,你的驱动可以调用IWDFIoRequest2::GetEffectiveIoType得到IO请求的数据访问方式。
Using Buffered I/O in UMDF Drivers
如果你的驱动使用缓冲区IO,UMDF根据请求的不同类型其行为也不同。对于读和写请求,驱动主持进程创建一个中间空间给驱动访问。
对于写请求,驱动主持进程在调用驱动堆栈之前,传输调用者应用程序的输入空间的内容到输入空间。驱动从中间空间读到输入信息然后将其写入到设备中。
对于读请求,驱动从设备读取数据,并将其装入到中间空间。驱动主持进程将中间空间的输出数据拷贝到应用程序的输出空间中。
对于设备IO控制请求,驱动主持进程创建两个独立的空间给驱动访问。这一点和WDM,KMDF对于读写,IO控制请求的缓冲区IO的方式不一样,它们只使用一个中间缓冲区。
对于怎样选择缓冲区IO的方式,可以查看WDF_DEVICE_IO_TYPE。
Using Direct I/O in UMDF Drivers
Using Neither Buffered I/O nor Direct I/O in UMDF Drivers
这种方式UMDF是不支持的,然而对于一些IOCTL指定的"neither"方法,UMDF可以转换这样的设备IO控制请求为缓冲区IO或者直接IO.
1, 在你的驱动的INF文件中,指定UmdfMethodNeitherAction节。告诉UMDF,应该转发那些使用了“neither”访问驱动方法的设备IO请求。
2,通过UMDF提供的缓冲区IO或者直接IO来访问IO请求空间。
你应该在你确信UMDF可以转换访问方法为缓冲区IO或者直接IO.使能对“neither”访问方法的IOCTL请求。如果IOCTL指定的请求不支持相关的 Buffer Descriptions for I/O Control Codes空间规范,UMDF就不能转换这些空间。