Windows驱动_文件系统过滤驱动之十

            在这里差不多一个月的时间了,暂时不是很忙,我应该在这里的这段时间,没有人打扰,都是自己的时间,好好的提高自己,虽然我现在不知道,我写这些博客是否真的有用,但作为修身养性的一种方法也不为错,它可是使自己单独的思考,宁静的思考,后面会在合适的时机,买一本游戏引擎的书,和一本网络的书进行学习。

            今天我们看一下,文件系统过滤驱动使用完成例程。

            文件系统过滤驱动使用完成例程的方法和设备驱动使用其的方法基本一致,完成例程处理IRP完成时的一些操作。任何将IRP传递给下层驱动的驱动例程都可以通过调用IoCallDriver之前调用IoSetCompletionRoutine的方法为其处理的IRP设置完成例程。

            IRP完成例程的定义如下:


           NTSTATUS 
          (*PIO_COMPLETION_ROUTINE) ( 
             IN PDEVICE_OBJECT DeviceObject, 
             IN PIRP Irp, 
             IN PVOID Context 
           ); 


           完成例程一般在IRPL <= DISPATCH_LEVEL的随意线程上下文环境中被调用。


            因为,完成例程可以在DISPATCH_LEVEL上被调用,所以完成例程不能调用那些小于其IRQL的例程,比如IoDeleteDevice,在完成例程中必须为其使用的数据结构分配非分页内存。


            完成例程怎样被执行:


           完成被调用一般分为两类,第一类执行在随意的线程上下文空间中,IRQL <= DISPATCH_LEVEL,如下的操作被执行:


           每一个为其IRP注册的完成例程,从IRP最底层堆栈进行调用,如果某一个完成例程返回STATUS_MORE_PROCESS_REQUIRED,完成例程处理被停止。


           如果IRP包含一个内存描述列表(MDL),被MDL映射的任何物理内存页被解锁。


           I/O完成例程的第二阶段被发送到目标线程进行排队,作为一个特殊的内核APC.


           第二类,完成例程执行在原始IO请求的上下文空间里,它作为一个内核APC执行,因此其运行的IRQL为APC_LEVEL,在这一类中,如下操作被执行:


           如果IRP代表空间的操作,Irp->AssociatedIrp.SystemBuffer的内部被拷贝到Irp->UserBuffer中。


           如果IRP包含MDL,MDL被释放。


           Irp->IoStatus的内容被拷贝到Irp->UserIosb中,以至于IO请求的原始拥有者可以得到操作的最终状态。


           如果在Irp->UserEvent中提供了一个事件,它被置于右信号状态,如果这个IRP有一个文件对象,它也被置于右信号状态。


           如果这个IRP是通过IoBuildDeviceIoControlRequest或IoBuildSynchronoutFsdRequest调用创建,它被从停止的线程IO请求列表中移除。


           如果调用请求一个APC,用户APC被排队。


           IRP被释放。


           如果处理IRP的完成例程返回STATUS_MORE_PROCESSING_REQUIRED,IRP完成例程操作被停止,可以对同一个IRP调用IoCompleteRequest恢复,当这个发生,返回STATUS_MORE_PROCESSING_REQUIRED上面的完成例程的第阶段立刻开始执行。


           检查PendingReturned标志:


           如果完成例程没有置事件为由信号状态,完成例程就必须检查Irp->PendingReturned标志,如果这个标志被设置,完成例程必须调用IoMarkIrpPending标志IRP为等待状态.


           在完成例程中返回状态:


           一般文件系统过滤驱动完成例程会返回如下的NTSTATUS值给调用者。


           STATUS_SUCCESS.


           STATUS_MORE_PROCESSING_REQUIRED


           STATUS_SUCCESS表示驱动已经完成了IRP的处理,允许IO管理器继续进行完成IRP的操作。


           如果另外的NTSTATUS值被返回,IO管理器将其重置为STATUS_SUCCESS.


           传递IRP和完成例程的处理举例:


           为了将IRP传递给下层驱动并为其设置完成例程,必须做如下的操作。


           调用IoCopyCurrentIrpStackLocationToNext将当前IRP的堆栈参数拷贝到下层IRP堆栈。


           调用IoSetCompleteRoutine例程为IRP注册一个完成例程。
 
           调用IoCallDriver,将IRP传递给下层驱动。


           IoCopyCurrentIrpStackLocationToNext( Irp ); 
           IoSetCompletionRoutine( Irp,                                 // Irp
                        MyLegacyFilterPassThroughCompletion, // CompletionRoutine
                        (PVOID)recordList,                   // Context
                        TRUE,                                // InvokeOnSuccess
                        TRUE,                                // InvokeOnError
                        TRUE);                               // InvokeOnCancel
           return IoCallDriver ( NextLowerDriverDeviceObject, Irp ); 


           IoSetCompletionRoutine的第一个参数是指向IRP的指针,第二个参数是完成例程的名字,第三个指向由驱动定义的一个结构体的指针,这个结构体指针会传递给完成例程,这个结构体包含一些上下文信息,完成例程在处理这个IRP的时候需要这些信息。这个结构体必须在非分页内存分配,因为完成例程可以在DISPATCH_LEVEL的IRQL上被调用。


           后面的三个参数,指定在什么情况下,完成例程被调用,可以指定在成功,失败,取消三种状态下分别指定。


           当派遣例程设置了完成例程,很快从调用IoCallDriver后返回后,必须检查IRP的PendReturned标志,如果标志设置,调用IoMarkIrpPending,然后返回STATUS_SUCCESS.


        if (Irp->PendingReturned) {
              IoMarkIrpPending( Irp );
        }
            return STATUS_SUCCESS;


           完成例程的好处:


           设置完成例程,可以允许驱动在下层驱动完成了该IRP以后,还可以对IRP进行进一步的处理,完成例程可以根据基于IO的原始请求来决定如何进行进一步的处理。


           完成例程的缺陷:


           因为完成例程是在随意的线程上下文空间,并在IRQL <= DISPATCH_LEVEL上被调用,所以完成例程只能对IRP进行有限制的操作。


           完成例程的调用约定:


           下面有些说明,可以避免我们在完成例程中一些普通的编程错误。


           IRQL相关的约定:


           因为完成例程可以在DISPATCH_LEVEL上被调用,所以必须遵照如下的约定。


           完成例程中,不能够安全的调用那些需要比DISPATCH_LEVEL更低优先级的内核例程,比如IoDeleteDevice或ObQueryNameString.
    
           完成例程使用任何数据或者结构体,空间必须由非分页内存中分配。


           完成例程的代码不能处于分页代码中。


           完成例程不能得到资源,互斥体,快速互斥体,但是可以得到自旋锁。


           检查PendingReturn标志:


           除非完成例程使事件有效,否则它必须检查Irp->PendingReturn标志。如果标志被设置,完成例程必须调用IoMarkIrpPending,标志IRP在停滞状态。


           如果完成例程使用了信号事件,它就不应该调用IoMarkIrpPending.


           返回值的约定:


           文件系统过滤驱动完成例程只能返回STATUS_SUCCESS或者STATUS_MORE_PROCESSING_REQUIRED,所有的其它返回值,都被IO管理器重置为STATUS_SUCCESS。


           返回STATUS_MORE_PROCESSING_REQUIRED约定:


           在下列的三种情况,完成例程应该返回STATUS_MORE_PROCESSING_REQUIRED.


           当派遣例程正在等待完成例程设置一个事件,在这种情况下,应该返回STATUS_MORE_PROCESSING_REQUIRED,为了避免IO管理器在完成例程返回后过早的结束IRP.


           当完成例程发送IRP到工作队列而相应的派遣例程已经返回STATUS_PENDING.在这种情况下,返回STATUS_MORE_PROCESSING_REQUIRED避免IO管理器在完成例程返回后过早的结束IRP.


           驱动本身通过调用IoAllocateIrp或IoBuildAsynochronousFsdRequest创建IRP,它的上层驱动不会接收到IRP,它可以在完成例程中直接调用IoFreeIrp安全的是否IRP,在这种情况下,完成例程必须返回STATUS_MORE_PROCESSING_REQUIRED只是再没有另外的完成例程要处理IRP.


           完成例程不能为一个锁定操作返回STATUS_MORE_PROCESSING_REQUIRED,互锁操作不能被等待,派遣例程不能对这些操作返回STATUS_PENDING.
  
           发送IRP到工作队列的约定:


           如果完成例程需要邮寄一个IRP到工作队列,在邮寄前,必须调用IoMarkIrpPending。否则,在调用IoMarkIrpPending之前IRP不能从队列中离队,被另一个驱动完成,被系统释放,因此产生一个CRASH.


           在文件系统过滤驱动中追踪每一个流的内容:


           文件流,流的内容,每一个流的内容:


           一个文件流就是为了装载文件数据的一字节化序列。通常,一个文件只拥有一个文件流,作为文件默认的数据流。然而,一个文件在不同的文件系统上支持不同的多个数据流,其中有一个没有命名的,作为默认的数据流。其它的,命名作为指定的数据流。当你打开一个文件的时候,实际上,是你打开了指定文件的流。


           当文件系统第一次打开一个文件流,它会创建一个文件系统指定的流的上下文结构,文件控制块(FCB)或者流控制块(SCB),这些结构的地址都在文件对象的FsContext成员指向的空间中保存。


           对于本地的文件系统,如果已打开的文件流被再次打开(比如,共享读),IO子系统会创建另外一个文件对象,但是文件流不会创建另外一个新的流上下文结构。两个文件对象接收到同一个流上下文结构的地址。所以,在本地文件系统中,流上下文结构的指针唯一标识文件流。


            对于支持每个流上下文的网络文件系统,如果使用同一个共享名或者IP地址打开一个已经打开的文件流,文件流的行为跟本地文件系统一样。IO子系统创建一个新的文件对象,但是不会创建一个新的流上下文。它会分配同一个FsContext指针给两个共享的文件对象。然而,如果文件流被不同路径再次打开(不同的共享名,或者IP地址),文件系统创建一个新的流上下文。因此,对于网络文件系统同支持一一对应的流上下文结构。FsContext指针不会唯一代表一个文件流。


            每一个流上下文结构是过滤驱动定义的一个结构,它包含了一个FSTRL_PER_STREAM_CONTEXT结构。过滤驱动使用这个结构来追踪每一个被文件系统打开的文件流的信息。


            在XP以后的系统中,这个结构中必须包含一个FSRTL_ADVANCED_FCB_HEADER结构。


            文件系统拥有指定文件流的每一个流上下文的链表,当文件系统创建一个新的流上下文结构(FSRTL_ADVANCED_FCB_HEADER对象)为文件流,它首先调用FsRtlSetupAdvancedHeader初始化这个链表,当文件系统过滤驱动调用FsRtlInsertPerStreamContext,每一个流上下文被创建并被加到全局链表中。


            当文件系统为文件流删除它的流上下文结构时,它调用FsRtlTeardownPerStreamContexts释放所有过滤驱动用于连续文件流的所有每一个流上下文结构。这个例程为了全局链表中的每一个流上下文结构调用FreeCallback例程。FreeCallback例程必须假设文件流的文件对象已经被释放。


            为了去查询,文件系统中给定的文件对象是否对于文件流支持每一个流的上下文结构,可以在文件对象上调用FsRtlSuportPerStreamContexts.注意,文件系统可能对于特定的文件类型支持每一个流上下文结构,而其他的类型不支持。举例来说,NTFS和FAT对于分页文件不支持每一个流上下文结构。因此,对于一个文件流FsRtlSupportPerStreamContexts返回TRUE,并不代表对于所有文件流都返回TRUE.  


            文件系统过滤驱动创建预前流的上下文:


            文件系统过滤驱动当文件流第一次打开时会文件流创建预前流上下文结构,虽然预前流上下文结构可以在任何涉及到文件流操作时都可以创建。


            分配预前流结构上下文。


            预前流上下文结构可以被分配在分页或者非分页空间。一般调用ExAllocatePoolWithTag分配:


        
            contextSize = sizeof(SPY_STREAM_CONTEXT) + fileName.Length;
            ctx = ExAllocatePoolWithTag(NonPagedPool, 
                            contextSize,
                            MYLEGACYFILTER_CONTEXT_TAG);  


            如果过滤驱动是在分页空间中分配的预前流上下文结构,它不能在其完成例程中调用ExAllocatePoolWithTag.因为完成例程可以运行在DISPATCH_LEVEL的IRQL上。


            初始化预前流上下文结构:


            文件系统过滤驱动调用FsRtlInitPerStreamContext初始化预前流上下文结构。这个例程初始化上下文结构的FSRTL_PER_STREAM_CONTEXT.(这个结构是过滤驱动指定的)。


             注意:如果你的过滤驱动值为每一个文件流创建一个预前流上下文结构,你应该传递给例程FsRtlInitPerStreamContext的Instanceld参数设置为NULL.


             过滤驱动可以在任何时候初始化预前流上下文结构,但是它必须在结构和文件流内容关联之前初始化。


             关联预前流结构上下文和文件流:


             当文件系统成功的处理IRP_MJ_CREATE请求打开流以后,预前流上下文结构才可以和文件流关联上。这是因为,文件系统处理了创建请求以后,文件对象的FsContext指针才对于文件系统过滤驱动有效。因为FsContext指针唯一标识文件流,文件系统需要查明过滤驱动已经看到的代表文件的文件对象,哪个过滤驱动已经创建了预前流上下文,因为这个原因,通常过滤驱动在创建的派遣例程(pre-create)创建预前流上下文结构,在创建的完成例程(post-create)删除这个上下文结构。


             为了去检查同样的一个文件流是否被另一个预前流上下文结构相关联,文件系统过滤驱动可以调用FsRtlLookupPerStreamContext.
 
             如果FsRtlLookupPerStreamContext对同一个文件流找到一个存在的预前流上下文,过滤驱动应该删除新创建的预前流上下文结构。


             如果FsRtlLookupPerStreamContext没有找到你的过滤驱动预前为文件流已经创建的预前流上下文结构,过滤驱动可以调用FsRtlInsertPerStreamContext使新创建的流上下文结构和文件就进行相关联。


             为预前的流上下文调用FsRtlInsertPerStreamContext以后,文件系统将承担其结构的删除和释放责任。如果你的过滤驱动分配了预前流上下文结构而没有为其调用FsRtlInsertPerStreamContext,你的过滤驱动需要调用ExFreePool将其释放。


             删除预前流上下文结构:


             当一个预前流上下文结构和文件流相关联而不再需要后,它应该被删除,流上下文被如下两种方式删除:


             手动,当过滤驱动调用FsRtlRemovePerStreamContext.


             自动,当文件系统调用过滤驱动的FsRtlTeardownPerStreamContexts,过滤驱动在里面调用FreeCallBack例程释放流上下文结构。


             删除预前流上下文结构的时间:


             当文件流还在打开状态,过滤驱动需要为其删除预前流上下文结构,它首先调用FsRtlRemovePerStreamContext,删除给定文件相关联的上下文结构的全局链表中的内容。在调用FsRtlRemovePerStreamContext后,过滤驱动释放其上下文结构。


            在你的过滤驱动调用FsInsertPerStreamContext将其预前流上下文结构和文件流相关联以后,过滤驱动必须调用在释放其结构前调用FsRtlRemovePerStreamContext,否则系统在文件流关闭的时候会CRASH.


            预前流上下文结构的FreeCallback回调函数什么时候被调用:


            当文件流正在被关闭或者删除的时候,文件系统为其文件流释放自己的流上下文结构。在这个似乎后,文件系统也会调用FsRtlTeardownPerStreamContexts,然后调用为文件流注册的全局链表中的预前流上下文结构的FreeCallback回调函数。(FreeCallback例程在过滤驱动在调用FsRtlInitPerStreamContext初始化预前流山下文结构的时候注册的)


            注意:在你的过滤驱动已经调用FsRtlInsertPerStreamContext将其预前流上下文结构和文件流相关联以后,文件系统将承担在没有任何对打开的流技能型任何操作之后确保过滤驱动的预前流上下文结构的FreeCallback函数被调用。


            在原始文件系统过滤驱动中追踪预前文件上下文件的内容:


            原始文件系统过滤驱动可以通过用户定义的信息结构FSRTL_PER_FILE_CONTEXT对象记录一些关于文件内容的信息。


            不是所有的文件系统,都支持预前文件上下文对象。为了查找文件系统的一个相关的文件是否支持,可以调用FsRtlSupportsPerFileContexts宏。


            使用FsRtlInitPerFileContext宏初始化FSRTL_PER_FILE_CONTEXT对象,然后使用FsRtlInsertPerFileContext例程关联随意上下文对象和文件。


            使用FsRtlGetPerFileContextPointer宏得到一个指向文件系统实时运行库包的指针,追踪文件内容。


            过滤驱动可以使用FsRtlLookupPerFileContext例程找到和指定的文件相关联文件上下文对象。这个例程可以通过指定结构的拥有者和结构的实例从而缩小查找范围。


            过滤驱动可以通过调用FsRtlRemovePerFileContext删除一个上下文对象,这个例程可以指定结构的拥有者或结构的实例去缩小查找范围。


            在文件还在打开状态下,只能调用FsRtlRemovePerFileContext例程删除上下文对象,千万不要和FsRtlTeardownPerFileContexts混淆。


             文件系统在文件拆除的时候,调用FsRtlTeardownPerFileContexts释放所有还和文件控制块(FCB)相关联任何过滤驱动的上下文件结构。FsRtlTeardownPerFileContexts例程调用在FSRTL_PER_FILE_CONTEXT对象中指定的为每个过滤上下文定义的FreeCallback函数。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值