随学随记,暂时未经编程验证 Written by HOOK_TTG(Jamie Jiang)
7) DispatchCleanup例程
驱动程序的DispatchCleanup例程用来处理带有IRP_MJ_CLEANUP的I/O功能代码的IRPs。
驱动程序可以使用DispatchCleanup例程来执行任何清理操作,所有的文件对象句柄被关闭之后需要执行这些操作。要注意的是,DispatchCleanup例程在关闭最后的句柄的进程的进程上下文中被调用。这个进程可能与做出打开这个句柄的进程不同。(发生这个不同的典型情况是,另一个进程使用了DuplicateHandle用户模式例程来复制了这个进程句柄。)如果驱动程序必须要在最初的进程上下文中执行清理操作,可以使用PsSetCreateProcessNotifyRoutine例程注册一个回调例程,但要是记住的是这些回调例程是有限的系统资源。
一般来说,DispatchCleanup例程必须通过下列操作为当前在设备队列中的每个IRP处理一个IRP_MJ_CLEANUP请求(或者在驱动程序的IRPs内部队列),也为关联文件对象的目标设备对象执行清理操作:
l 调用IoSetCanceRontine来设置Cancel例程的指针为NULL。
l 取消当前用于目标设备对象的队列中的每个IRP,如果在的驱动程序的已排队的IRP的 I/O栈存储单元中的文件对象,匹配接收到的IRP_MJ_CLEANUP请求的I/O栈存储单元中的文件对象。
l 调用IoCompleteRequest来完成IRP,并返回STATUS_SUCCESS。
在处理IRP_MJ_CLEANUP请求时,驱动程序可能会接收到更多的请求,比如IRP_MJ_READ或者IRP_MJ_WRITE。因此,驱动程序必须释放资源,也必须与其他派遣例程同步执行其DispatchCleanup例程,比如DispatchRead和DispatchWrite。
8) DispatchRead、DIspatchWrite和DispatchReadWrite例程
驱动程序的DIspatchRead和DispatchWrite例程分别处理IRP_MJ_READ和IRP_MJ_WRITE I/O功能代码的IRPs。
另一种方法,一个合并的DispatchReadWrite例程可以以上两个I/O功能代码的IRPs。
一个设备的数据要能传送到系统,那么其每个驱动程序必须有一个DispatchRead例程。一个设备要能接收来自从系统传送的数据,那么其每个驱动程序必须有一个DispatchWrite例程。任何要双向传输数据的驱动程序可以有一个合并的DispatchReadWrite例程。
较低层驱动程序异步的处理IRP_MJ_READ和IRP_MJ_WRITE请求。因此,倘如这个请求在驱动程序的IRP I/O栈存储单元中有个有效参数,那么顶层驱动程序的DispatchRead和/或者DispatchWrite例程必须传递这些请求用于进一步处理。
驱动程序建立其用于缓冲的还是直接I/O的设备对象将影响它如何传递请求。特别是,使用直接I/O来做DMA操作的驱动程序可能需要将大传输请求分解成小传输请求序列,以此来平衡IRP_MJ_READ或者IRP_MJ_WRITE请求。
下面小节讨论在使用缓冲I/O和直接I/O的底层设备驱动程序中对设计和实现DispatchReadWrite例程的考虑,以及在它们层次之上的较高层驱动程序。
1. 处理异步传输
除了顶层驱动程序,所有驱动程序都异步处理IRP_MJ_READ和IRP_MJ_WRITE。DispatchRead和DispatchWrite例程,甚至在顶层驱动程序中,不能等待低层驱动程序处理完成一个异步读取或者写入请求;它们必须向下一层驱动程序传递这样一个请求并且返回STATUS_PENDING。
同样的,一个底层设备驱动程序的DispatchReadWrite例程必须传递这个传输请求到其他处理设备I/O请求的驱动程序的例程,然后返回STATUS_PENDING。
一个高层驱动程序有时必须建立局部IRPs并且将其传递到下一层驱动程序。高层驱动程序要完成这个原始的读取/写入IRP,只能在其局部传输轻轻被下一层驱动程序处理完毕后。
例如,一个SCSI类型驱动程序的DispatchReadWrite例程必须分解超过底层HBA的传输能力的大的传输请求为一组局部传输请求。类型驱动程序必须在建立其局部传输IRPs中建立参数,以便SCSI端口/微端口驱动程序可以在一个单DMA操作上满足每个局部传输请求。
其他使用DMA或者PIO的设备驱动程序也可能需要分解大的传输请求为多个部分。
2. 使用缓冲I/O的DispatchReadWrite
任何建立其用于缓冲I/O的设备对象的底层设备驱动程序是通过返回从设备传输到一个锁定的系统空间缓冲器Irp->AssociatedIrp.SystemBuffer中的数据来满足一个读取请求。它通过从同样的缓冲器将数据传送到设备来满足一个写入请求。
因此,这样一个设备驱动程序的DispatchReadWrite例程通常做下列操作,在接收到一个传输请求之后:
a) 调用IoGetCurrentIrpStackLocation并确定传输请求的方向。
b) 检查请求的参数有效性。
l 对于一个读取请求,这个例程通常检查驱动程序的IoStackLocation->Parameters.Read.Length的值来确定缓冲区的大小是否足够放得下接受自设备传输来的数据。例如,系统键盘类型驱动程序只处理来自Win32用户输入线程的读取请求。
这个驱动程序定义了一个数据结构,KEYDOARD_INPUT_DATA,用来保存来自设备的击键,在一些特定情况,在击键进来时,为了能满足读取请求,会在一个内环缓冲器中保持一些数量的这些结构。
l 对于一个写入请求,此例程通常检查Parameters.Write.Length的值,,如果必要的话,还要检查Irp->AssociateIrp.SystemBuffer的数据的有效性:也就是说,它的设备只接受包含规定值范围的结构数据包。
c) 如果有参数无效,那么DispatchReadWrite例程则立即完成这个IRP,就像Completing IRPs章节描述的那样。否则,这个例程传递这个IRP给其他驱动程序例程做进一步处理,就像Passing IRPs down the Driver Stack章节描述的那样。
使用缓冲I/O的底层设备驱动程序必须读取或者写入请求发起者指定大小的数据来满足一个传输请求。这样的一个驱动程序很可能为数据定义了一个数据结构,用于进来的或者发送到它的设备的数据,还可能在内部缓冲结构化的数据。
在内部缓冲数据的驱动程序应该支持IRP_MJ_FLUSH_BUFFERS请求,并且也可以IRP_MJ_SHUTDOWN请求。
在一链路的顶层驱动程序通常负责检查输入IRP的参数,在传递一个读取/写入请求到下一层驱动程序之前。因此,许多下一层驱动程序可以假定它们的在一个读取/写入IRP中的I/O栈存储单元具有有效地参数。如果在一链路的一个底层驱动程序知道设备特定的数据传输约束条件,那么驱动程序就必须在其I/O栈存储单元中参数的有效性。
3. 使用直接I/O的DispatchReadWrite
任何建立用于直接I/O的设备对象的较低层设备驱动程序,通过返回从设备传输过来的数据到系统物理内存来满足一个读取请求。这个系统物理内存由MDL在Irp->MdlAddress描述。它通过从系统物理内存传输数据到设备来满足一个写入请求。
较低层驱动程序必须异步处理读取和写入请求。因此,每个较低层驱动程序的DispatchReadWrite例程必须传递带有效参数的IRP_MJ_READ和IRP_MJ_CREATE IRPs到其他驱动程序例程,就像Passing IRPs down the Driver Stack章节描述的那样。
对于被发送到较低层驱动程序的读取和写入IRPs来说,MDL在Irp->MdlAddress中描述的分页的物理内存已经被试探是否拥有正确的访问权限来执行请求的传输,而且已经被链路中顶层驱动程序或者I/O管理器锁定。任何建立用于直接I/O的设备对象的中间层或者底层驱动程序,不要调用MmProbeAndLockPages,因为这个操作已经被执行过了。底层驱动程序应该调用MmGetSystemAddressForMdlSafe。(适用于Windows98的驱动程序调用MmGetSystemAddressForMdl作为替代。用于Windows Me、Windows2000和更近版本的操作系统应该使用MmGetSystemAddressForMdlSafe。)
任何中间层或者底层设备驱动程序的DispatchReadWrite例程应该验证其读取或者写入IRPs的I/O栈存储单元中的参数,如果它不能信任一个较高层驱动程序传递下来的IRPs具有有效地参数。如果DIspatchReadWrite例程返现一个参数错误,那么它应该适用一个合适的错误STATUS_XXX值来完成这个IRP。如果参数有效,中间层驱动程序的DispatchReadWrite例程必须传递这个请求以求进一步处理。
一个底层驱动程序的DispatchReadWrite例程必须随同这个传输请求一起调用IoMarkIrpPending,传递这个IRP以求其他驱动程序例程做进一步处理,并且返回STATUS_PENDING。
需要注意的是,设备驱动程序的DispatchReadWrite例程可以控制已排队IRPs的顺序,为了提高到其设备的I/O吞吐量,可以通过随同一个驱动程序确定的关键值一起调用IoStartPacket。驱动程序的另一个例程稍后出列这个IRP,确定请求的长度是否必须分解成一些局部传输操作,以及安排设备来传输数据。
一般来说,一个设备驱动程序必须分解大的传输请求以适应这些因素,就是它的设备可能推迟这些操作直到建立了用于一个给定的传输请求的设备。这样一个设备驱动程序的DispatchReadWrite例程可以不因为一些设备特定传输约束条件而检查传入的IRPs的I/O栈存储单元,也不用试图计算局部传输范围,驱动程序可以推迟这些检查直到它的StartIO(或者其他驱动程序例程)安排了用于一个传输操作的设备的前一刻。
4. 高层驱动程序中的DispatchReadWrite
除了文件系统驱动程序,一个较高层驱动程序通常不需要有任何用于IRPs的内部队列。这样的一个驱动程序的DispatchReadWrite例程可以传递带有效参数的IRPs到下一层驱动程序,可能在建立其IoCompletion例程之后。
然而,如果有必要,在其传递一个带有主功能代码 IRP_MJ_READ或者IRP_MJ_WRITE的IRP到SCSI端口/微端口驱动程序对之前,一个SCSI类型驱动程序的DispatchReadWrite例程要负责分解大的传输请求。
如果一个较高层驱动程序分配了一个或者多个IRPs,这些IRPs是在其DispatchReadWrite例程中为紧邻的下一层驱动程序建立的,为了申请某些数量的局部传输,那么这个DispatchReadWrite例程必须使用每个驱动程序分配的IRP调用IoSetCompletionRoutine。驱动程序必须注册它的IoCompletion例程来跟踪在每个局部传输操作中的数据多少,这样IoCompletion例程就可以释放所有驱动程序分配的IRPs,最后,完成原始的请求。
如果底层驱动程序控制了一个可移动介质设备,任何高层驱动程序分配的IRPs都必须有一个线程上下文。为了建立一个线程上下文,这个分配驱动程序必须在每个新分配的IRP中设置Irp->Tail.Overlay.Thread为同样的值,这些值来自传入的传输IRP。
如果底层驱动程序返回一个带有错误的局部传输IRP,IoCompletion例程可以重发这个局部传输请求,也可以使用返回的错误设置原始IRP的I/O状态块来完成这个原始IRP,随后释放任何较高层驱动程序分配的IRPs和内存。
如果一个较高层驱动程序的DispatchReadWrite例程为局部传输操作分配内存并且这些内存可以被驱动程序的IoCompletion例程(或者底层设备驱动程序)访问到,那么DispatchReadWrite例程必须从非分页池中分配这些内存。
5. Read/Write派遣例程概要
在实现DispatchRead、DispatchWrite或者DispatchReadWrite例程的时候要记住以下几点:
l 在分层的驱动程序链中,顶层驱动程序有责任在IRP中建立紧邻的下一层驱动程序的I/O栈存储单元之前,检查传入的读取/写入IRPs参数的有效性。
l 中间层和最底层驱动程序通常可以依赖他们链路中的顶层驱动程序传递下来的请求传输请求都具有有效参数。然而,有些驱动程序可以对其IRP的I/O栈存储单元中的参数执行检查,而且每个设备驱动程序可能为某些可能违反其设备强制限制条件而检查参数。
l 如果一个DispatchReadWrite例程使用一个错误来完成一个IRP,那么应该使用一个合适的NTSTATUS-类型值设置I/O栈存储单元Stack成员,设置Information成员为0,并且连同这个IRP和一个值为IO_NO_INCREMENT的参数PriorityBoost一起调用IoCompleteRequest。
l 如果一个驱动程序使用缓冲I/O,那么它可能需要定义一个结构,包含要传输的数据,而且还需要在内部缓冲一些数量的这些结构。
l 如果一个驱动程序使用直接I/O,那么它可能需要检查在Irp->MdlAddress的MDL描述的一个缓冲器是否包含了太多数据(或者太多页面分段),对于底层设备来说在一个单传输操作处理来说太多了。如果是太多了,那么驱动程序就必须分解原始传输请求为一个较小传输操作序列。
一个紧密连接的类型驱动程序可以在其DispatchReadWrite例程中为底层端口驱动程序将这样一个请求分解。SCSI类型驱动程序,通常用于大容量存储设备,就必须这么做。
l 一个较低层设备驱动程序的DispatchReadWrite例程应该推迟分解一个大传输请求为局部传输,直到另一个驱动程序例程为建立用于这个传输的设备而将这个IRP出列。
l 如果一个较低层设备驱动程序排队一个读取/写入IRP用于其自己的例程更进一步处理,那么在它排队这个IRP之前必须调用IoMarkIrpPending。在这种情况下,DIspatchReadWrite例程也必须带STATUS_PENDING返回控制。
l 如果DIspatchReadWrite例程传递一个IRP到下一层驱动程序,那么它必须为下一层驱动程序在IRP中建立I/O栈存储单元。在传递这个IRP给IoCallDriver之前,较高层驱动程序是否还要在IRP中设置一个IoCompletion例程,取决于这个驱动程序的设计以及其分层下的那些驱动程序的设计。
然而,一个较高层驱动程序分配了任何资源,必须IRPs或者内存,那么它必须在调用IoCallDriver之前调用IoSetCompletionRoutine。它的IoCompletion例程必须释放任何驱动程序分配的资源,在下一层驱动程序已经完成这个请求的时候,而且要在IoCompletion例程连同原始的IRP调用IoCompleteRequest之前。
l 如果一个较高层驱动程序为下一层驱动程序(可能包含一个底层可移动介质设备驱动程序)分配IRPs,那么这个分配驱动程序必须在其分配的每个IRP中建立线程上下文。