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

来源:互联网 发布:光晕2windows live id 编辑:程序博客网 时间:2024/05/22 21:10
          越是现在这个时候,就越应该加油,我不应该昏昏的过,我不想以后后面的自己跟现在一样,我不应该跟别人一样。吃得苦上苦,方为人上人,我应该跟之前一样。现在蛮好,不受别人的重视,其实也无所谓,我正想要这样的时间,宝贵的时间,可以自己做点东西出来。还是千万不要浮躁,还是用之前自己喜欢的那句话吧。总有一天,我也会破解成蝶,飞向天空。


          文件系统过滤驱动的派遣例程:


          文件系统过滤驱动的派遣例程跟设备驱动的派遣例程一样,一个派遣例程处理一种或多种IRP.(IRP的类型通过主函数代码,就是驱动对象中的主功能函数指针列表),这些派遣函数,我们一般在DriverEntry例程的驱动对象的函数列表进行设置注册。当某一个IRP发送到驱动,IO子系统根据IRP的主功能码,调用相应的派遣例程进行IRP的处理。

       NTSTATUS 
    (*PDRIVER_DISPATCH) ( 
    IN PDEVICE_OBJECT DeviceObject, 
    IN PIRP Irp 
    ); 


           一般来说,文件系统过滤驱动的派遣函数都运行在PASSIVE_LEVEL中断优先级,在原始发送请求的用户线程的上下文空间中。但是有时候也有例外。举例来说,页错误,导致在APC_LEVEL上面调用读写派遣例程。后面我们会详细说明。当然,我们也不能避免在过滤链中在IRPL > PASSIVE_LEVEL上调用IoCallDriver(举例来说,释放自旋锁或快速互斥体失败),我们强烈建议在被调用的派遣函数中的IRQL跟我们要调用IoCallDriver的IRQL保持一致。


           当然,我们的派遣例程也可以在分页代码中,但是必须遵照Making Driver Pageable说明执行。


           如果文件系统过滤驱动拥有控制设备对象(CDO),它的派遣例程必须能够探测其IRP的目标设备对象是CDO而不是绑定卷上的VDO.下面,我们具体来讨论派遣函数可以进行的操作。


           完成IRP:


           每个派遣函数都会接收到一个指向IRP的目标设备对象的指针,一般就是DeviceObject参数,如果过滤驱动拥有CDO,派遣例程应该在对IRP进行任何操作前,先检查这个DeviceObject是否其CDO.


           文件系统过滤驱动的CDO不需要支持任何类型IO操作,但是CDO必须完成所有发送给其的请求。


           完成一个IRP,派遣例程必须执行如下这些步骤:


        1,设置Irp->IoStatus.Status为一个合适的值。


        2,调用IoCompleteRequest返回IRP给IO管理器。


        3,返回和步骤1同样的status值给调用者。


             完成一个IRP,通常就是成功或者失败IRP.


             成功完成一个IRP,一般就设置其NTSTATUS为STATUS_SUCCESS.
 
             失败一个IRP,通常就是返回一个错误或警告值,比如,STAUTS_INVALID_DEVICE_REQUEST或STAUS_BUFFER_OVERFLOW.


             NTSTATUS值定义在ntstatus.h中,这些一般分为四类,成功,信息,警告,错误。


             注意:虽然STATUS_PENDING是一个成功NTSTATUS值,但是一般视为错误的返回值。


            将IRP传递到驱动堆栈的下一层驱动:


             在检查完IRP的目标设备对象后,派遣例程默认会调用IoCallDriver将IRP传递给驱动堆栈的下一层驱动,特别需要注意的是,过滤驱动千万不要简单将不识别或者自己不处理的IRP直接返回错误,而不传递给下层驱动,因为这样可能导致操作系统本身以不可预知的方式崩溃。比如,如果文件系统过滤驱动接到IRP_MJ_PNP时候,返回失败,它将干扰电源管理的系统休眠操作。这也就是为什么,文件系统过滤驱动不涉及到电源管理,不会受到IRP_MJ_POWER请求的原因。


             除了直接完成IRP外,没有设置完成例程的派遣例程应该使用由IoCallDriver调用后的返回值进行返回。除了返回值为STATUS_PENDING外,我们必须确保这个返回值跟Irp->IoStatus.Status设置的值一致。


              如果一个派遣例程设置了一个完成例程,发送IRP到工作队列,应该遵照如下的说明进行操作:


         1,返回有IoCallDriver调用的返回值。


         2,等待完成例程的调用,发出事件信号,返回值到Irp->IoStatus.Status中。


         3,标记IRP为等待状态,将它发送到工作队列,然后返回STAUTS_PENDING.
  
         4, 如果完成例程中发送IRP到工作队列,派遣例程必须标记IRP为等待状态并返回STATUS_PENDING.
          
              当然,这上面的说明也不一定正确,取决于当时指定的操作,比如目录改变通知,不能同步,有些oplocks,不能异步等,详细的,我们将在下面介绍。


              举例说明,没有设置完成例程的派遣例程怎样将IRP传递给驱动堆栈中的下一层驱动。


        1,首先调用IoSkipCurrentIrpStackLocation移除当前的IRP堆栈位置,告诉IO管理器在IRP的结束的流程中不要查找其完成例程。


        2,调用IoCallDriver将IRP传递到驱动堆栈中的下一层驱动.


        IoSkipCurrentIrpStackLocation ( Irp ); 
            return IoCallDriver ( NextLowerDriverDeviceObject, Irp ); 


        或者:


        IoSkipCurrentIrpStackLocation ( Irp ); 
        status = IoCallDriver ( NextLowerDriverDeviceObject, Irp ); 
        /* log or debugprint the status value here */
         return status; 
        
        IoCallDriver的第一个参数是下一层驱动的设备对象,第二个参数是指向IRP的指针。


                这里我们应该强调, 在没有设置完成例程的派遣函数里,都应该调用IoSkipCurrentIrpStackLocation 进行操作,因为这样很方便。


                缺点是,这里指向IRP的指针,传递给IoCallDrive后,不再有效,我们不能解引用,从而不能释放其资源。如果要释放其资源还是应该设置完成例程。


                 注意:如果我们调用IoSkipCurrentIrpStackLocation,就不能为其设置完成例程。 
        
                 派遣例程的约束:


                 IRQL方面的约束:


                 使用分页IO的派遣例程不能在任何高于APC_LEVEL的IRQL上面调用IoCallDriver,如果派遣例程临时提升了IRQL,必须在调用IoCallDriver前,降低其IRQL。


                 使用分页IO,比如读写的派遣例程,不能安全调用需要运行在IRQL在PASSIVE_LEVEL上的内核例程。


                 使用分页IO的派遣例程,不能安全调用需要运行在IRQL小于DISPATCH_LEVEL上的内核例程。


                 使用非分页IO的派遣例程,不应该在高于PASSIVE_LEVEL上面的IRQL上调用IoCallDriver,如果派遣例程临时提升了IRQL,必须在调用IoCallDriver前,将其降低。


                 处理IRP的约束:


                 如果IRP的参数包含任何用户模式的地址,在使用前,必须进行测试。


                 如果IPR包含一个IOCTL或FSCTL空间,从32位平台传输到64位平台,空间的内容需要进行检查。


                 不像文件系统,文件系统过滤驱动禁止在调用ExAcquireFastMutexUnsafe or ExAcquireResourceExclusiveLite.前调用FsRtlEnterFileSystem或者FsRtlExitFileSystem,因为FsRtlEnterFileSystem和FsRtlExitFileSystem会禁止大多数文件系统需要的普通的内涵APC。


                完成IRP的约束:


                当结束一个IRP的时候,文件系统过滤驱动因该只适用成功或这错误的返回值。


                 虽然STATUS_PENGING是一个成功类型NTSTATUS值,但是我们应该将其视为错误的返回值。


                 在派遣例程调用IoCompleteRequest后,其IRP的指针不再有效,不能安全进行卸载操作。


                 设置完成例程的约束:


                 当派遣例程调用IoSetCompletionRoutine设置完成例程时,可以传递一个指向上下文空间的结构体指针,当处理这个IRP的时候,完成例程可以使用这个上下文空间,这个结构体空间必须分配为不分页的空间,因为完成例程可以在DISPATCH_LEVEL上被调用。


                 如果派遣例程设置完成例程,并返回STATUS_MORE_PROCESSING_REQUIRED,必须进行如下的操作,避免IRP预前结束。


                 标志IRP为等待状态,调用IoCallDriver并返回STATUS_PENDING.


                 调用KeWaitForSingleObject等待完成例程执行,然后调用IoCompleteRequest结束IRP。


                 传递IRP的约束:


                 派遣例程调用IoCallDriver后,IRP的指针不再有效,不能被安全的卸载,除非派遣例程等待完成例程被调用以后,在可以进行安全的卸载。


                 禁止在文件系统过滤驱动中调用PoCallDriver例程(因为,文件系统过滤驱动不会接收到IRP_MJ_POWER的请求)


                 返回状态的约束:


                 除了直接完成IRP外,没有设置完成例程的派遣例程应该使用由IoCallDriver调用后的返回值进行返回。除了返回值为STATUS_PENDING外,我们必须确保这个返回值跟Irp->IoStatus.Status设置的值一致。
 
                 当调用IoCallDriver返回STATUS_PENDING,派遣例程也应该返回STATUS_PENDING,除非它等待完成例程将事件置为由信号。


                 当发送IRP到工作队列进行进一步处理,派遣例程应该标记IRP为等待状态,并返回STATUS_PENDING .


                 当设置的完成例程,将IRP发送到工作队列进行进一步处理,派遣例程应该标记IRP为等待状态并返回STATUS_PENDING.


                 标志IRP为pengidng状态的派遣例程必须返回STATUS_PENDING.


                 Oplock操作不应该在pended状态(发送到工作队列),派遣例程不能为他们返回STATUS_PENDING.


                 发送IRP到工作队列的约束:


                 如果派遣例程要发送IRP到工作队列,在此之前必须调用IoMarkIrpPending。否则,IRP不能在队列中被另外的驱动删除,或者完成,不能被系统释放,而导致系统CRASH.


                 派遣例程的IRQL和线程上下文。


           Dispatch routine                    Caller's IRQL:         Caller's thread context:
           Cleanup                                     PASSIVE_LEVEL                   Nonarbitrary
           Close                                         APC_LEVEL                         Arbitrary
           Create                                        PASSIVE_LEVEL                   Nonarbitrary
           DeviceControl (except paging I/O)   PASSIVE_LEVEL                   Nonarbitrary
           DeviceControl (paging I/O path)      APC_LEVEL                         Arbitrary
           DirectoryControl                           APC_LEVEL                          Arbitrary
           FlushBuffers                                PASSIVE_LEVEL                   Nonarbitrary
           FsControl (except paging I/O)         PASSIVE_LEVEL                   Nonarbitrary
           FsControl (paging I/O path)            APC_LEVEL                         Arbitrary
           LockControl                                 PASSIVE_LEVEL                   Nonarbitrary
           PnP                                            PASSIVE_LEVEL                   Arbitrary
           QueryEa                                      PASSIVE_LEVEL                   Nonarbitrary
           QueryInformation                          PASSIVE_LEVEL                   Nonarbitrary
           QueryQuota                                 PASSIVE_LEVEL                   Nonarbitrary
           QuerySecurity                               PASSIVE_LEVEL                   Nonarbitrary
           QueryVolumeInfo                          PASSIVE_LEVEL                   Nonarbitrary
           Read (except paging I/O)                PASSIVE_LEVEL                   Nonarbitrary
           Read (paging I/O path)                   APC_LEVEL                         Arbitrary
           SetEa                                           PASSIVE_LEVEL                  Nonarbitrary
           SetInformation                              PASSIVE_LEVEL                   Nonarbitrary
           SetQuota                                      PASSIVE_LEVEL                   Nonarbitrary
           SetSecurity                                   PASSIVE_LEVEL                   Nonarbitrary
           SetVolumeInfo                              PASSIVE_LEVEL                   Nonarbitrary
           Shutdown                                     PASSIVE_LEVEL                   Arbitrary
           Write (except paging I/O)                PASSIVE_LEVEL                   Nonarbitrary
           Write (paging I/O path)                   APC_LEVEL                         Arbitrary