概述
Minifilter即File System Minifilter Drivers,是Windows為了簡化第三方開發人員開發文件過濾驅動而提供的一套框架,這個框架依賴於一個稱之為Filter Manager(后面簡寫為FltMgr)的傳統文件系統過濾驅動。這套框架應用到內核中的結構如下圖所示:
FltMgr以傳統文件過濾驅動的形式插入到I/O處理隊列中去接收不同的I/O請求,然后將這個請求遍歷發布到它所維護的Minifilter對象中,然后根據各個Minifilter對這個I/O請求的處理結果來決定后續的操作。
這種模式在很多軟件架構中使用,類似於插件一樣,每一個Minifilter遵守一定的接口規范插入到FltMgr中,然后就能執行過濾控制。
上面是對整個Minifilter框架進行描述,實際內部需要注意的細節牽涉到很多內核知識,比如中斷級別、I/O請求被封裝成IRP包和fast I/O請求等相對復雜的概念。這里不做深究,主要以研究Minifilter的開發過程和相應注意的地方為主。此外還有FltMgr在內核中的位置等相關知識,也只在必要的地方提及一下。
Minifilter流程
Minifilter通過注冊並啟動后,根據在注冊表中設置的相應值,插入到對應的FltMgr實例(Frame)隊列中,然后關聯上需要過濾的卷。FltMgr會根據Minifilter所注冊的I/O操作類型,調用對應的pre和post操作函數,並根據對應的返回值執行不同的流程。這里假設有A、B、C三個Minifilter從上往下掛載在FltMgr實例上,那么當接收到I/O請求時,執行的步驟為A-pre、B-pre、C-pre,一旦某個Minifilter返回了FLT_PREOP_COMPLETE,即表明這個I/O請求被它完成處理了,則立即按照相反的順序調用對應的post函數(不再繼續往下調)。
注冊和關聯
Minifilter根據FltRegisterFilter函數介紹說明,為響應操作設置對應的回調函數和數據結構后,就可以在DriverEntry中調用FltRegisterFilter進行注冊了,然后調用FltStartFiltering來通知FltMgr當前Minifilter已經准備好關聯到要過濾的卷了。
FltMgr會在系統啟動時或者接收到手動關聯命令時,調用Minifilter的InstanceSetupCallback回調函數,當然前提是Minifilter在注冊的時候增設置了這個函數的地址,這個回調函數會傳入要關聯的卷標信息,FltMgr會根據這個函數的返回值判斷是否關聯成功。這里和對應卷相關聯的Minifilter實例叫做instance。一個卷上可以掛多個instance。只有對指定卷進行關聯后,才能過濾其I/O請求。
取消關聯的過程可以理解成釋放實例的過程,釋放后就無法繼續過濾對應卷了。
Pre回調函數(PFLT_PRE_OPERATION_CALLBACK)
FLT_PREOP_COMPLETE:表示當前的過濾驅動完成了本次I/O操作,過濾管理器就不再往下發送本次I/O請求,而是依次向上調用post回調函數。這種情況下IoStatus.Status的值就是最終I/O操作的執行結果(不能是STATUS_PENDING)。
FLT_PREOP_SUCCESS_NO_CALLBACK/FLT_PREOP_SUCCESS_WITH_CALLBACK:這個返回值表示處理成功,讓過濾管理器去做自己的事,區別在於WITH_CALLBACK的返回值會標明需要回調post函數。而NO_CALLBACK的則標明不需要。
FLT_PREOP_PENDING:顧名思義,表明當前過濾驅動將本次I/O操作掛起了,過濾管理器需要等待當前驅動調用FltCompletePendedPreOperation函數后才會繼續本次I/0操作處理流程。注意只有對於基於IRP中斷的I/O操作(用FLT_IS_IRP_OPERATION宏測試)才可以掛起。
FLT_PREOP_DISALLOW_FASTIO:只有操作是fast I/O操作(用FLT_IS_FASTIO_OPERATION(Data)進行測試)時才可以返回這個值,表明過濾驅動不允許fast I/O操作繼續執行。因此過濾管理器不會再下發該請求,而是依次向上調用post回調函數。這種情況下不需要設置IoStatus.Status的值,過濾管理器會自動設置這個值。
FLT_PREOP_SYNCHRONIZE:這個返回值表明處理未完成,保持當前過濾驅動上下文線程環境,交由過濾管理器繼續下發后調用post回調函數后繼續處理。也只對基於IRP中斷的操作有效,並且必須有post函數,如果不是基於IRP中斷的,就會和FLT_PREOP_SUCCESS_WITH_CALLBACK一樣。注意:對於Create操作,不應該返回這個值,因為文件管理器已經為這個操作進行同步了。此外對於同步的讀和寫操作,如果返回這個值會嚴重影響驅動和系統性能。
如果在pre和post函數中更改了Data的內容,必須調用FltSetCallbackDataDirty函數(更改IoStatus除外)。
Post回調函數(PFLT_POST_OPERATION_CALLBACK)
這個回調函數執行的中斷等級為IRQL <= DISPATCH_LEVEL。所以需要注意以下幾點:1、不能安全調用必須低於IRQL級別的任何內核模式的派遣函數。2、在這個函數內開辟的任何數據結構必須位於非頁內存。3、該函數不可分頁。4、不能請求資源(resource)、信號量(mutextes)和快速信號量(fast mutexes),只能獲取互斥鎖(spin lock)。5、不能獲取、設置或者刪除上下文,但可以釋放上下文。
相對於Pre回調函數多了最后一個參數Flags,這個參數如果存在FLTFL_POST_OPERATION_DRAINING標記位,則表明當前過濾驅動實例正在被取消關聯,本次調用是為了清理pre回調函數傳入的completion context,返回值必須是FLT_POSTOP_FINISHED_PROCESSING,並且這個回調是在IRQL<=APC_LEVEL執行的。
FLT_POSTOP_FINISHED_PROCESSING:表明本過濾驅動完成了對I/0操作的處理並將控制交還給過濾管理器。
FLT_POSTOP_MORE_PROCESSING_REQUIRED:只有當微過濾驅動將本次I/O操作發送到工作隊列中時,才能返回這個值,微過濾驅動最后必須負責完成這個I/O操作。過濾管理器會繼續等待FltCompletePendedPostOperation函數被調用后才繼續執行控制。注意:這個返回值也必須對基於IRP中斷的操作執行。
任何要被執行在IRQL<DISPATCH_LEVEL的I/O完成處理都不能在這個回調函數內直接執行。取而代之,可以使用FltDoCompletionRpocessingWhenSafe或者FltQueueDeferredIoWorkItem之類的函數發送到工作隊列中。
除了以下情況外,要確保在Flags參數沒有FLTFL_POST_OPERATION_DRAINING標記位的時候才能調用FltDoCompletionRpocessingWhenSafe函數:
1、如果微過濾驅動的pre回調函數為一個基於IRP中斷的操作返回FLT_PREOP_SYNCHRONIZE,那么對應的post回調要保證和pre回調都處在IRQL<=APC_LEVEL的線程上下文中。
2、對於create操作的post回調要保證中斷級別為IRQL_PASSIVE_LEVEL(原始IRP_MJ_CREATE操作所處的線程上下文)。