檔案系統Minifilter驅動(十)
在Minifilter驅動中管理上下文
上下文是minifilter驅動定義的一個結構,可以與一個filter管理器物件關聯起來. Minifilter驅動可以為以下型別的物件建立和設定上下文:
· 檔案(僅Vista及以後版本.)
· 例項
· 卷
· 流
· 流控制代碼(檔案物件)
· Transactions (僅Vista及以後版本.)
除了捲上下文必須從非分頁池中分配外,上下文可以從分頁或非分頁池中分配.
當上下文繫結的物件被刪除,或minifilter驅動例項從一個卷斷開,或minifilter驅動被解除安裝時,Filter管理器會自動刪除上下文.
本節包括:
一、註冊上下文型別
當minifilter驅動從它的DriverEntry例程中呼叫FltRegisterFilter時,它必須註冊每一種它要使用的上下文的型別.
要註冊上下文型別,minifilter驅動得建立一個長度可變的FLT_CONTEXT_REGISTRATION結構組並將其的一個指標儲存到FLT_REGISTRATION結構(minifilter驅動將此結構傳給FltRegisterFilter的入參Registration)的成員ContextRegistration 中. 此結構組中的元素順序不分先後.不過,結構組中的最後一個元素必須是{FLT_CONTEXT_END}.
對每一種minifilter驅動使用的上下文型別來說,它必須以FLT_CONTEXT_REGISTRATION結構的形式提供至少一個上下文的定義.每一個FLT_CONTEXT_REGISTRATION結構都定義了上下文的型別、大小和其他資訊.
當minifilter驅動呼叫FltAllocateContext 建立了一個新的上下文時,filter管理器用FltAllocateContext 例程的Size 引數以及FLT_CONTEXT_REGISTRATION 結構的成員Size和Flags 來選擇要使用的上下文定義.
對固定尺寸的上下文來說, FLT_CONTEXT_REGISTRATION 結構的成員
對尺寸可變的上下文來說,Size成員必須被設定為FLT_VARIABLE_SIZED_CONTEXTS. Filter管理器直接從分頁或非分頁池匯中分配尺寸可變的上下文.
可以在FLT_CONTEXT_REGISTRATION結構的成員Flags中,指定標記FLTFL_CONTEXT_REGISTRATION_NO_EXACT_SIZE_MATCH. 如果minifilter驅動使用固定尺寸的上下文且指定了此標記,如果上下文的尺寸大於或等於請求的尺寸,filter管理器就會從lookaside列表中分配一個上下文.
對一個給定的上下文型別來說,minifilter驅動可以提供至多三個固定尺寸的上下文定義(其中每一個的尺寸都不同)和一個尺寸可變的定義.更多資訊,參考 FLT_CONTEXT_REGISTRATION.
Minifilter驅動可以隨意地提供一個上下文cleanup callback例程來在該上下文被free之前被呼叫.詳情參考PFLT_CONTEXT_CLEANUP_CALLBACK.
Minifilter驅動可以隨意地定義它自己的分配和free上下文的callback例程,而非依靠filter管理器去執行這些任務.不過, 這是及其必要的. 更多資訊,參考 PFLT_CONTEXT_ALLOCATE_CALLBACK和PFLT_CONTEXT_FREE_CALLBACK.
以下從CTX minifilter驅動例子中節選的程式碼,展示了一個用於註冊例項、檔案、流和檔案物件(流控制代碼)上下文的FLT_CONTEXT_REGISTRATION結構組.
const FLT_CONTEXT_REGISTRATION contextRegistration[] = { { FLT_INSTANCE_CONTEXT, //ContextType 0, //Flags CtxContextCleanup, //ContextCleanupCallback CTX_INSTANCE_CONTEXT_SIZE, //Size CTX_INSTANCE_CONTEXT_TAG }, //PoolTag { FLT_FILE_CONTEXT, //ContextType 0, //Flags CtxContextCleanup, //ContextCleanupCallback CTX_FILE_CONTEXT_SIZE, //Size CTX_FILE_CONTEXT_TAG }, //PoolTag { FLT_STREAM_CONTEXT, //ContextType 0, //Flags CtxContextCleanup, //ContextCleanupCallback CTX_STREAM_CONTEXT_SIZE, //Size CTX_STREAM_CONTEXT_TAG }, //PoolTag { FLT_STREAMHANDLE_CONTEXT, //ContextType 0, //Flags CtxContextCleanup, //ContextCleanupCallback CTX_STREAMHANDLE_CONTEXT_SIZE, //Size CTX_STREAMHANDLE_CONTEXT_TAG }, //PoolTag { FLT_CONTEXT_END }};
二、建立上下文
只要某個minifilter驅動已經註冊了它要使用的上下文型別,它就可以呼叫FltAllocateContext 來建立一個上下文.這個例程會依照註冊上下文型別一文中描述的標準來選擇合適的上下文定義.
以下從CTX minifilter驅動例子中節選的程式碼中,CtxInstanceSetup例程呼叫FltAllocateContext 來建立一個例項上下文:
status = FltAllocateContext( FltObjects->Filter, //Filter FLT_INSTANCE_CONTEXT, //ContextType CTX_INSTANCE_CONTEXT_SIZE, //ContextSize NonPagedPool, //PoolType &instanceContext); //ReturnedContext
在CTX中,以下上下文定義是為例項上下文註冊的:
{ FLT_INSTANCE_CONTEXT, //ContextType 0, //Flags CtxContextCleanup, //ContextCleanupCallback CTX_INSTANCE_CONTEXT_SIZE, //Size CTX_INSTANCE_CONTEXT_TAG }, //PoolTag
這是一個固定尺寸的上下文定義,因為Size 成員是一個常量. (如果Size 成員是FLT_VARIABLE_SIZED_CONTEXTS則它是尺寸可變的上下文定義.)注意成員Flags中FLTFL_CONTEXT_REGISTRATION_NO_EXACT_SIZE_MATCH標記沒有被設定. 在此情形之下, FltAllocateContext 的引數Size值匹配上下文定義的Size成員的值,FltAllocateContext 從相應的非分頁lookaside列表中分配例項上下文.如果值不匹配,FltAllocateContext 會失敗且返回值為STATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND.
FltAllocateContext 初始化新上下文上的引用計數為1.當不再需要此上下文時,minifilter驅動必須release這個引用.因此對FltAllocateContext 的每一次呼叫都必須匹配一個後來的FltReleaseContext呼叫.
三、設定上下文
在建立了一個新上下文之後,minifilter驅動可以呼叫FltSetXxxContext (Xxx是上下文的型別)來把它繫結到一個物件上.
如果FltSetXxxContext例程的引數Operation被設定為FLT_SET_CONTEXT_KEEP_IF_EXISTS,那麼僅當minifilter驅動還沒有為該物件設定一個上下文時FltSetXxxContext 才會繫結最新分配的上下文到該物件上.如果minifilter驅動已經設定了一個上下文, FltSetXxxContext 就會返回STATUS_FLT_CONTEXT_ALREADY_DEFINED(一個NTSTATUS錯誤程式碼)且不會取代現有的上下文.如果FltSetXxxContext 例程的OldContext 引數不為NULL,則它會收到現有上下文的指標.當不再需要這個指標時,minifilter驅動必須呼叫FltReleaseContext來release它.
如果引數Operation 被設定為FLT_SET_CONTEXT_REPLACE_IF_EXISTS,那麼FltSetXxxContext 就會繫結新的上下文到物件上.如果minifilter驅動已經設定了一個上下文,FltSetXxxContext 就會刪除現有的上下文,設定新的上下文並增加新上下文上的引用計數.如果引數OldContext 不為NULL,它會收到已刪除的上下文的指標.當不再需要這個指標時,minifilter驅動必須呼叫FltReleaseContext來release它.
以下從CTX minifilter驅動例子中節選的程式碼中,CtxInstanceSetup 例程建立並設定一個例項上下文:
status = FltAllocateContext( FltObjects->Filter, //Filter FLT_INSTANCE_CONTEXT, //ContextType CTX_INSTANCE_CONTEXT_SIZE, //ContextSize NonPagedPool, //PoolType &instanceContext); //ReturnedContext...status = FltSetInstanceContext( FltObjects->Instance, //Instance FLT_SET_CONTEXT_KEEP_IF_EXISTS, //Operation instanceContext, //NewContext NULL); //OldContextif (instanceContext != NULL) { FltReleaseContext(instanceContext);}return status;
注意在呼叫FltSetInstanceContext之後,有一個FltReleaseContext 呼叫來release由FltAllocateContext(不是FltSetInstanceContext)設定的引用計數.其解釋在Release上下文一文中.
四、獲得上下文
只要minifilter驅動已經為一個物件設定了一個上下文,它就可以呼叫FltGetXxxContext 例程來獲得該上下文.
以下從SwapBuffers minifilter驅動例子中節選的程式碼中, minifilter驅動呼叫FltGetVolumeContext 來獲得一個捲上下文:
status = FltGetVolumeContext( FltObjects->Filter, //Filter FltObjects->Volume, //Volume &volCtx); //Context...if (volCtx != NULL) { FltReleaseContext(volCtx);}
如果FltGetVolumeContext 的呼叫成功,引數Context 就會收到呼叫者的捲上下文的地址. FltGetVolumeContext 會增加Context 指標上的引用計數.因此,當不再需要這個指標時,minifilter驅動必須呼叫FltReleaseContext 來release它.
五、引用上下文
Filter管理器用引用計數來管理一個minifilter驅動上下文的生存期.引用計數是一個指示上下文的狀態的數字.只要一個上下文被建立或被一個系統元件引用,它的引用計數就會增加1.當不再需要上下文時,它的引用計數就會被消耗.一個正的引用計數值意味著上下文可用.當引用計數減為零時,該上下文就不可用且filter管理器最終會free它.
Minifilter驅動可以呼叫FltReferenceContext增加上下文的引用計數來新增它自己對該上下文的引用.這個引用必須最終通過呼叫FltReleaseContext來移除.
六、Releasing上下文
Minifilter驅動呼叫FltReleaseContext來release一個上下文.對下列例程之一的每一次成功呼叫都必須最終匹配一個對FltReleaseContext的呼叫:
FltAllocateContext
FltGetXxxContext
FltReferenceContext
注意FltSetXxxContext返回的OldContext指標和FltDeleteContext返回的Context指標不再需要時也必須被Release.
以下從CTX minifilter驅動例子中節選的程式碼中, CtxInstanceSetup 例程建立並設定一個例項上下文,然後呼叫FltReleaseContext:
status = FltAllocateContext( FltObjects->Filter, //Filter FLT_INSTANCE_CONTEXT, //ContextType CTX_INSTANCE_CONTEXT_SIZE, //ContextSize NonPagedPool, //PoolType &instanceContext); //ReturnedContext...status = FltSetInstanceContext( FltObjects->Instance, //Instance FLT_SET_CONTEXT_KEEP_IF_EXISTS, //Operation instanceContext, //NewContext NULL); //OldContextif (instanceContext != NULL) { FltReleaseContext(instanceContext);}return status;
注意無論FltSetInstanceContext 是否成功都呼叫了FltReleaseContext.即無論成功與否呼叫者必須呼叫FltReleaseContext 來release由FltAllocateContext (不是FltSetInstanceContext)設定的引用.
如果例項的上下文被成功地設定,FltSetInstanceContext就會新增它自己對該例項上下文的引用.因此,不再需要被FltAllocateContext 設定的引用,然後對FltReleaseContext的呼叫會移除它.
如果對FltSetInstanceContext的呼叫失敗,例項上下文僅有一個引用,也就是由FltAllocateContext設定的那個.當FltReleaseContext 返回時,例項上下文的引用計數會變為零,然後它會被filter管理器free.
七、刪除上下文
每一個FltSetXxxContext 成功設定的上下文都必須最終被刪除.不過,當上下文繫結的物件被刪除、一個minifilter驅動例項從一個捲上斷開或minifilter驅動被解除安裝時filter管理器會自動刪除它們.因此,minifilter驅動極其有必要明確地刪除一個上下文.
Minifilter驅動可以呼叫FltDeleteXxxContext或呼叫FltDeleteContext來刪除一個上下文.
僅當一個上下文當前因為某個物件而被設定時,它才可以被刪除.如果它還沒有被設定或它已經被FltSetXxxContext呼叫成功地取代了那它就不能被刪除.
在對FltDeleteXxxContext的呼叫中,如果OldContext 引數不為NULL則它會返回舊的上下文.如果它為NULL,則filter管理器就會消耗上下文上的引用計數,除非minifilter驅動在其上還有未決的引用否則它就會被free.
以下程式碼說明如何刪除一個流上下文:
status = FltDeleteStreamContext( FltObjects->Instance, //Instance FltObjects->FileObject, //FileObject &oldContext); //OldContext...if (oldContext != NULL) { FltReleaseContext(oldContext);}
上例中,FltDeleteStreamContext 從流中移除流上下文,但它沒有消耗上下文的引用計數,因為OldContext 引數不為NULL. FltDeleteStreamContext 會在引數OldContext中返回已刪除的上下文的地址. 在執行完一切所需的處理之後,呼叫者必須呼叫FltReleaseContext來release這個上下文.
八、Freeing上下文
一個上下文會在它被刪除且所有對它的未決引用都已經被release之後被free掉.
這裡有此規則的一個例外:如果某個上下文已經被建立但還沒有呼叫FltSetXxxContext,它就不需要被刪除.當它的引用計數變為零時它會被free. (參考Release上下文一文中的例子程式碼.)
當某個minifilter驅動註冊了它的上下文型別,每一個上下文定義都可以隨意地包括一個在該上下文被free之前會被呼叫的上下文cleanup callback例程. 更多資訊參考PFLT_CONTEXT_CLEANUP_CALLBACK.
九、檔案系統對上下文的支援
要支援檔案上下文(如果適用的話)、流上下文和檔案物件(流控制代碼)上下文,一個檔案系統必須使用FSRTL_ADVANCED_FCB_HEADER結構.微軟的Windows檔案系統都適用了這個結構,所有第三方的檔案系統開發者也都被鼓勵這麼做.詳情看FsRtlSetupAdvancedHeader和FSRTL_ADVANCED_FCB_HEADER.
NTFS和FAT檔案系統不支援在pre-create中或post-close中或IRP_MJ_NETWORK_QUERY_OPEN操作的分頁檔案上的檔案、流或檔案物件上下文.
minifilter驅動可以分別呼叫FltSupportsStreamContexts和FltSupportsStreamHandleContexts來決定一個檔案系統是否支援流上下文和一個給定檔案物件的檔案物件上下文.
Vista及以後版本上檔案上下文可用.
對支援per檔案僅一個單一資料流的檔案系統(比如FAT)來說,檔案上下文等價於流上下文.這樣的檔案系統通常都支援流上下文但不支援檔案上下文.取而代之的是,filter管理器利用檔案系統對流上下文的現有的支援提供了這個支援.對繫結到這些檔案系統上的minifilter驅動例項來說,FltSupportsFileContexts 返回FALSE, 同時FltSupportsFileContextsEx 返回TRUE (當Instance 引數中是一個非NULL的有效值時).
要支援檔案上下文,一個檔案系統必須:
· 嵌入一個PVOID型別的FileContextSupportPointer 成員到它的檔案上下文結構中,通常是FCB.檔案系統必須初始化此成員為NULL.
· 用FsRtlSetupAdvancedHeaderEx(而非 FsRtlSetupAdvancedHeader)來初始化它的流上下文結構,為了引數FileContextSupportPointer傳一個有效指標給FileContextSupportPointer成員(被嵌入到相應的檔案上下文結構中). 詳看FsRtlSetupAdvancedHeaderEx和FSRTL_ADVANCED_FCB_HEADER.
· 當它刪除對一個檔案的檔案上下文結構時,呼叫FsRtlTeardownPerFileContexts來free已被過濾器和minifilter驅動關聯到該檔案上的所有的檔案上下文結構.
十、最優方法
如果minifilter驅動僅為per卷建立一個minifilter驅動例項的話,它應該使用例項上下文而非捲上下文來謀得更高效能.
Minifilter驅動也可以通過儲存一個minifilter驅動例項上下文的指標到它的流或流控制代碼上下文中來提高效能.