XiangXiangRen
总版主
总版主
  • 注册日期2003-02-22
  • 最后登录2015-09-01
  • 粉丝13
  • 关注0
  • 积分1042分
  • 威望472点
  • 贡献值1点
  • 好评度145点
  • 原创分13分
  • 专家分1分
阅读:1768回复:0

微过滤器驱动开发指南 之三

楼主#
更多 发布于:2005-06-20 22:52
8.操作回调数据参数

8.1 I/O参数块(I/O Parameter Block)

就像前面所提及的,回调数据(Callback Data)包含了两个关于I/O的重要的数据结构:
1)I/O状态块(I/O Status Block):       Data->IoStatus用来返回它自己所结束的操作的状态(或者在后操作回调中可以读取下层驱动所完成的操作的状态)。
2)I/O参数块(I/O Parameter Block):       Data->Iopb指向这个I/O操作中专为本微过滤器使用的数据。
本节深入讨论读取和改变这些参数的问题。

Iopb中的主功能号和辅功能号表示IRP/FastIo/FsFilter-主/辅功能,主功能号不能由过滤器修改。过滤管理器不支持。
TargetFileObject表示I/O操作的目标,流的文件对象。微过滤器可以修改它(必须调用FLT_SET_CALLBACK_DATA_DIRTY()),而且被过滤管理器承认。
TargetInstance参数与I/O从一个实例向另一个实例传递的意义不同,是表示当前的实例。过滤器可以改变这个指针。但是仅仅能是同一个层级,但绑定在其他的卷上的实例。同时,也必须调用FLT_SET_CALLBACK_DATA_DIRTY()。

微过滤器是不能修改TargetInstance指向同一个卷上的另一个实例的。举个例子来说,一个微过滤器在一个卷C:上有两个实例。一个在层级200称为实例C200,,另一个在层级100,称为实例C100,同时在D:上还有一个层级200的实例,称为实例D:200。现在假设在实例C200上IRP_MJ_READ的预操作回调被调用了,微过滤器可以把TrageInstance修改为实例D:200,但是不能修改为实例C:100。

这组织了任何微过滤器同一个卷的栈中非法的(不按固定顺序的)传递操作。
I/O参数块中含有一些与具体操作相关的参数。微过滤器必须用与操作配套的共用体结构来访问他们。对于IOCTL和FSCTL,具体的控制码也有不同的共用体结构。微过滤器必须检查具体的控制码来使用正确的共用体。

8.2 使用缓冲/MDL

在IRP的世界中,缓冲采用多种机制传递到驱动中。比如支持基本I/O的设备对象,缓冲是通过Irp->UserBuffer传入的,这对文件系统是最常见的。有些设备只支持缓冲I/O,那么I/O管理器传入的缓冲区Irp->AssociatedIrp.SystemBuffer.对于支持直接I/O的设备,缓冲是通过Irp->MdlAddress,一个被锁定的MDL。

但是可能有例外。一些IRP的栈空间的缓冲参数直接传入。可能指向核心内存或者是原始的用户空间内存。和设备对象对I/O操作的要求无关。这些缓冲不能传到硬件,因为他们无视设备对象所支持的I/O类型。

也有一些IRP总是缓冲I/O,比如IRP_MJ_QUERY/SET_INFORMATION.

对于微过滤器,缓冲总是通过适当的操作相关的共用体传入。没有一个通用的方法可以获得缓冲/MDL的地址来源。这种设计是为了减少栈空间之间缓冲导致的冲突。

在回调数据中,如果操作是缓冲型的,那么标记 FLTFL_CALLBACK_DATA_SYSTEM_BUFFER会被设置。如果是这样的,那么缓冲区在非分页的核心内存中。

如果这个标记没有设置,那么只能说明不是缓冲型。微过滤器有方法做进一步区分(见下一节)。缓冲还是可能来源于某个核心内存池。但是只要这个标记没有设置,过滤器就应该假设内存在原始用户空间中。这种情况下,如果缓冲并不是传入的用户缓冲,那么总是需要一个MDL来锁定一些页面,而且调用者必须获得一个系统空间地址来访问这些页面。这是一条规则,将在下节详叙之。

最后,对于某些微过滤器希望能够定位缓冲/长度/MDL来做一些最通用的操作的时候,FltDecodeParameters()提供用来做一个快速的查找并返回一个指向参数结构中的缓冲/长度/MDL。对于没有缓冲空间的操作,返回STATUS_INVALID_PARMETER.

NTSTATUS
FLTAPI
FltDecodeParameters(
    IN PFLT_CALLBACK_DATA CallbackData,
    OUT PMDL **MdlAddressPointer OPTIONAL,
    OUT PVOID  **Buffer OPTIONAL,
    OUT PULONG *Length OPTIONAL,
    OUT LOCK_OPERATION *DesiredAcces OPTIONAL
    );

DesiredAccess表示微过滤器可以使用的访问缓冲的方式。比如对于IRP_MJ_READ,可能指定IpWriteAccess,意味着这个缓冲区是可写的。对于IRP_MJ_WRITE,一般指定IoReadAccess表示微过滤器可以读取这个缓冲区但是不能修改它。这样一个应用使用一个仅仅有只读权限的页面来发起一个写请求也是合法的了。

对于希望缓冲空间被锁定的过滤器,应该了解:如果FLTFL_CALLBACK_DATA_SYSTEM_BUFFER表示设置了,那么可以假设这个缓冲区已经被锁定,可以安全的访问。

如果没有设置则可以调用FltLockUserBuffer()来锁定页面。这个调用可以保证页面被合适的方法锁定。如果成功了,它会设置与操作相关的参数部分的MdlAddress域为用来描述这些页面的MDL.

8.3 交换缓冲

一些微过滤器为了某些操作必须交换缓冲。考虑一个微过滤器实现加密算法,对一个非缓冲(non-cached)IRP_MJ_READ,它一般会希望把缓冲中的数据解密。同样的在写的时候,它希望把内容加密。考虑以下情况:内容无法在这个空间中加密。因为对于IRP_MJ_WRITE,这个微过滤器可能只有IoreadAccess权限。

因此微过滤器必须以他自己的有读写权限的缓冲区取代原来的缓冲区。加密了原缓冲区中的内容后写入新缓冲区后,再继续传递I/O请求。

为此,过滤管理器支持缓冲转换。有以下一些游戏规则必须遵守:
1.       改变了缓冲区的微过滤器必须有对应的后操作回调。这样缓冲能被过滤管理器自动的转换回来。
2.       如果改变的是一个标记有FLTFL_CALLBACK_DATA_SYSTEM_BUFFER标记的缓冲,必须保证新的缓冲是非分页内存。(也就是比如来自非分页内存池或者锁定的内存)。
3.       如果以上的标记没有设置,那么微过滤器必须按设备对象的要求来确定缓冲类型(可以在卷属性中查看DeviceObjectFlags等标记)。比如如果是支持直接I/O的,那么必须提供MDL等等。
4.       如果微过滤器使用非分页池中的缓冲来给一个没有设置FLTFL_CALLBACK_DATA_SYSTEM_BUFFER的操作,那么它也必须用MuBuildMdlForNonpagedPool()并把地址填写到MdlAddress域中。这是因为这么一来,下面的任何过滤器或者文件系统都不用再尝试去锁定非分页池(可以在构建的时候使用断言,但是对效率不利),如果提供了一个MDL,过滤器和文件系统总是可以通过MDL访问缓冲(可以获得一个系统内存地址来访问它)。
5.       替换一个缓冲的时候,微过滤器也必须换掉MDL(就是说缓冲和MDL要保持同步了)。对于通常的直接I/O异常,可以把MDL留空。
6.       微过滤器不应该释放旧的MDL和缓冲空间。
7.       不要尝试在后操作回调中替换掉旧的缓冲和MDL.过滤管理器自动执行这些操作。实际上微过滤器在后操作回调中的Iopb中见到缓冲空间和MDL是旧的(译者注:替换前的)。微过滤器必须自己在上下文中记录新的缓冲区。
8.       微过滤器应该释放自己分配的(和替换过的)缓冲。无论如何,如果有的话,过滤管理器会自动释放新缓冲的MDL。
9.       微过滤器不希望过滤管理器自动释放交换过的缓冲MDL可以调用FltRetainSwappedBufferMdl()。
10.       过滤器如果希望访问交换过的缓冲的MDL可以在后操作回调中使用FltGetSwappedBufferMdl()。既然一个更下层的过滤器或文件系统交换了新的缓冲空间进来,那么有可能生成了一个MDL。在后操作回调中微过滤器交换缓冲之前,过滤管理器保存了所有这样的的MDL。这个调用可以用来访问这些MDL.


9.上下文(Context)支持

所有的过滤器都必须在他们所操作的各种对象中记录一些他们自己的状态。让微过滤器在这些对象中拥有他们的上下文是过滤管理器的一个重要特点。

一个上下文是使用FltAllocateContext()分配一的一片动态内存区。这个调用传入需要的内存空间的大小并返回一个内存空间指针。系统会绑定一个内部的数据头用来跟踪这个指针所对应的上下文。

以下种类的对象支持上下文:

卷 - 一个已经挂载的设备。
实例 - 一个微过滤器对对一个卷的一次绑定。一个微过滤器可能对一个卷绑定多次。如果一个微过滤器对一个卷只能绑定一次,那么推荐用卷上下文来代替实例上下文,这样效率高多了。
文件 - 指关于一个文件的所有打开的流。一般这些上下文是不支持的。
流 - 文件上的一个单独的数据流。
流句柄 - 一个文件的一次打开,比如一个文件对象。

9.1 上下文注册

注册的时候,微过滤器定义它所想使用的上下文的类型,大小以及一个用来清理上下文的例程。微过滤器用一组FLT_CONTEXT_REGISTRATION结构来确定这些参数.

下面解释FLT_CONTEXT_REGISTRATION的细节:

ContextType:       注册的上下问的类型。
Flag:              表示这个上下文的一些特殊处理信息。当前的定义有:FLT_CONTEXT_REGISTRATION_NO_EXACT_SIZE_MATCH:默认的情况下,过滤管理器会比较一个给定的上下文的请求的长度和分配上下文的时候指定的数据长度。如果指定了这个标记,如果请求分配的内存大小少于等于注册的时候所指定的长度,过滤权利气回使用特殊指定的分配例程。当注册时候指定的大小为FLT_VARIABLE_SIZED_CONTEXT或者分配和释放例程都已经指定了的时候,这个标记被忽略。
CleanupContext:       当过滤管理器决定应该清理这个上下问的时候这个例程被调用。如果在上下文被释放之前没有什么需要清理的,这个可以设置为NULL.
Size:              这个上下文的字节大小。这用来允许过滤管理器使用内存池技术(如旁视列表)来让分配和释放更加有效率。如果使用了自己的分配和释放例程,这个域被忽略。
PoolTag:       分配内存的“池”上下文。这是使用ExAllocatePoolWithTag的时候用的。如果使用自己的分配和释放例程,这个域被忽略。
Allocate:       如果打算使用自己的分配例程,设置这个指针。如果打算依赖过滤管理器的内存池技术,那么这个应该设置为NULL.
Free:              如果想使用自己的释放例程,那么这个必须设置为非空。

如果一个微过滤器有相同类型的上下文但是长度不一,它可以为同类型的上下文注册不同的FLT_CONTEXT_REGISTRATION结构来利用过滤管理器的内存池技术。

9.2 上下文生成接口

下面是FltAllocateContext()的函数原型:
NTSTATUS
FLTAPI
FltAllocateContext (
    IN PFLT_FILTER Filter,
    IN FLT_CONTEXT_TYPE ContextType,
    IN SIZE_T ContextSize,
    IN POOL_TYPE PoolType,
    OUT PFLT_CONTEXT *ReturnedContext
);
ContextType可以是以下的情况:FLT_VOLUME_CONTEXT , FLT_INSTANCE_CONTEXT, FLT_FILE_CONTEXT, FLT_STREAM_CONTEXT, 或者是FLT_STREAMHANDLE_CONTEXT.

下面是一组用来把一个上下文绑定到一个对象上的例程。请注意上下文的类型和对象类型必须是配套的。

NTSTATUS
FLTAPI
FltSetVolumeContext (
    IN PFLT_VOLUME Volume,
    IN FLT_SET_CONTEXT_OPERATION Operation,
    IN PFLT_CONTEXT NewContext,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
    );

NTSTATUS
FLTAPI
FltSetInstanceContext (
    IN PFLT_INSTANCE Instance,
    IN FLT_SET_CONTEXT_OPERATION Operation,
    IN PFLT_CONTEXT NewContext,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
    );

NTSTATUS
FLTAPI
FltSetFileContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    IN FLT_SET_CONTEXT_OPERATION Operation,
    IN PFLT_CONTEXT NewContext,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
    );

NTSTATUS
FLTAPI
FltSetStreamContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    IN FLT_SET_CONTEXT_OPERATION Operation,
    IN PFLT_CONTEXT NewContext,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
    );

NTSTATUS
FLTAPI
FltSetStreamHandleContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    IN FLT_SET_CONTEXT_OPERATION Operation,
    IN PFLT_CONTEXT NewContext,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
);
设置一个上下文的时候有两种类型的操作可能发生。 它们是:

FLT_SET_CONTEXT_KEEP_IF_EXISTS:如果没有存在的上下文,那么将设置新的上下问。如果有存在的,那么新的上下文将不会设置上去,而且会返回一个错误代码。如果OldContext参数定义了,那么已经存在的上下文会返回。如果有,调用者必须释放返回的上下文。如果新的上下文没设置上去,那么调用者必须负责释放它。

FLT_SET_CONTEXT_REPLACE_IF_EXISTS: 即使旧的上下文存在,新的也回释放上去。如果OldContext定义了,那么被取代的上下文会返回。调用者必须自己释放它。如果没有定义,则旧的上下文会被自动释放。

当相关设备对象被系统释放,过滤管理器会在合适的时机调用微过滤器来清理上下文。微过滤器可能希望某个时候自己删除一个对象上的上下文。为此,微过滤器可以调用以下的一个例程来删除上下文。当然前提是它拥有这个上下文的指针。

VOID
FLTAPI
FltDeleteContext (
    IN PFLT_CONTEXT Context
    );

如果没有指针呢?它必须通过指定对象来删除上下文。可以使用以下这些例程中的某个:

NTSTATUS
FLTAPI
FltDeleteVolumeContext (
    IN PFLT_FILTER Filter,
    IN PFLT_VOLUME Volume,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
    );

NTSTATUS
FLTAPI
FltDeleteInstanceContext (
    IN PFLT_INSTANCE Instance,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
    );

NTSTATUS
FLTAPI
FltDeleteFileContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
    );

NTSTATUS
FLTAPI
FltDeleteStreamContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
    );

NTSTATUS
FLTAPI
FltDeleteStreamHandleContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    OUT PFLT_CONTEXT *OldContext OPTIONAL
);

如果OldContext参数为空,那么过滤管理器会释放对这个上下文的所有的引用。否则,上下文会通过OldContext返回。微过滤器必须自己调用FltReleaseContext来释放它。

9.3 上下文获取(Retrieval)接口

下面的例程用来获取某个设备的相关上下文。使用完毕,调用者必须释放返回的上下文,释放使用FltReleaseContext().上下文不能在DPC中断级获取。所以如果一个后操作回调中希望得到一个上下文,那么必须从预操作中获得并传入。

NTSTATUS
FLTAPI
FltGetVolumeContext (
    IN PFLT_FILTER Filter,
    IN PFLT_VOLUME Volume,
    OUT PFLT_CONTEXT *Context
    );

NTSTATUS
FLTAPI
FltGetInstanceContext (
    IN PFLT_INSTANCE Instance,
    OUT PFLT_CONTEXT *Context
    );

NTSTATUS
FLTAPI
FltGetFileContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    OUT PFLT_CONTEXT *Context
    );

NTSTATUS
FLTAPI
FltGetStreamContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    OUT PFLT_CONTEXT *Context
    );

NTSTATUS
FLTAPI
FltGetStreamHandleContext (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    OUT PFLT_CONTEXT *Context
);

当使用完毕,下面的一些例程用来释放获得的上下文。一般推荐不要在操作之间传递这些上下文指针。上下文的获取非常有效率,是专门设计用来在每个操作要使用的时候专门来获取上下文的。

VOID
FLTAPI
FltReleaseContext (
    IN PFLT_CONTEXT Context
);

类似实例通知例程,每个操作的回调例程都收到一个FLT_RELATED_OBJECTS结构。这个结构包含所有这个操作相关的所有已知的的对象。为了简化上下文的获取,有一个类似的FLT_RELATED_CONTEXT可以一次获取。这个结构如下:

typedef struct _FLT_RELATED_CONTEXTS {

    PFLT_CONTEXT VolumeContext;
    PFLT_CONTEXT InstanceContext;
    PFLT_CONTEXT FileContext;
    PFLT_CONTEXT StreamContext;
    PFLT_CONTEXT StreamHandleContext;

} FLT_RELATED_CONTEXTS, *PFLT_RELATED_CONTEXTS;

接下来两个例程用来依次获得多个例程,此外也有一次性释放。对于FltGetContexts()调用者指定(在DesiredContext参数中)需要的上下文。在内部,一次获得多个上下文比一个一个的获得它们效率高。当然,对于不需要的上下文最好是不要去获取它。FLT_ALL_CONTEXTS可以用来得到所有可用的上下文。

VOID
FltGetContexts (
    IN PFLT_RELATED_OBJECTS FltObjects,
    IN FLT_CONTEXT_TYPE DesiredContexts,
    OUT PFLT_RELATED_CONTEXTS Contexts
    );

VOID
FltReleaseContexts (
    IN OUT PFLT_RELATED_CONTEXTS Contexts
    );

9.4 上下文释放接口

当过滤管理器决定了一个上下文要被释放的时候,微过滤器的相关回调会被调用。这个回调例程应该清理任何上下文(包括清理分配的内存,释放资源等等)。通过返回,过滤管理器会释放传入的上下文结构。

每个类型的上下文都要有一个对应的清理例程。这些例程定义如下:

typedef VOID
(*PFLT_CONTEXT_CLEANUP_CALLBACK) (
    IN PFLT_CONTEXT Context,
    IN FLT_CONTEXT_TYPE ContextType
);
同一个清理例程可以注册给多个不同类型的上下文。




--------------------------------------------(待续)-----------------------------------------------------
本文翻译仅仅用做交流学习。我不打算保留任何版权或者承担任何责任。不要引用到赢利出版物中给
您带来版权官司。本文的翻译者是楚狂人,如果有任何问题,你可以通过邮箱
MFC_Tan_Wen@163.com,或者是QQ16191935,或者是MSN walled_river@hotmail.com与我交流。
游客

返回顶部