快速備份在昨天晚上的測試中 , 有一台機器在啟動過程中 , 還在 Native 模式時 , Win32 SubSystem 加載前 , 被斷點中斷 , 發現 MmGetSystemAddressForMdlSafe 傳回了空指針 .
Mdl->Flag = 0x42, MmGetSystemAddressForMdlSafe 将用 MmMapLockedPagesSpecifyCache 傳回 Read Buffer 的位址 .
修改 IP, 重新執行這個函數 ( 中間 , 有其他 IRP 被處理 ), 獲得 Buffer 的位址成功 .
以前從未發現過 MmGetSystemAddressForMdlSafe 函數傳回空指針的情況 . 而且 , 重新執行成功 , 表明其偶然性 .
經過分析 , 發現可能的原因 : MmGetSystemAddressForMdlSafe 因為系統虛拟位址空間緊張,而有可能傳回 NULL
此時 , 實體記憶體還剩約 1.5G, MDL 已經配置設定到了足夠的實體記憶體 , 但在被映射為虛拟位址時 , 卻失敗了 .
要求映射的位址範圍也比較大 , 為 0x1409000 Bytes. 可能的原因是因為系統此時無法配置設定出連續大約 21M 的虛拟位址空間 .
檢視了此時的驅動運作情況 , 發現 Irp 隊列中 , 存在大量的 Irp 同時被執行 ( 多到 Irp 隊列的連結清單在 Watch 視窗中已經無法繼續展開 , 展開 10 級以上 ). 因為快速備份的 LBA 重新映射過程 , 配置設定了大量的記憶體片段 ( 極有可能超過 1000 個 ), 可能導緻記憶體被撕為大量零碎的情況 , 在此種情況請求一個較大的連續虛拟位址空間時 , 由于不允許 Bugcheck, 是以傳回空指針 .
通常 , 我們在驅動中 , 頻繁請求的固定長度的記憶體是 , 會使用 LookasideList, 但 LookasideList 不能很好的支援大量的請求 , 因為 LookasideList 在發現其連結清單中沒有空閑的 Pool 時 , 仍然會使用 ExAllocatePool 從記憶體中直接配置設定 , 隻是釋放時 , 将其暫時放回 List, 以友善下次使用時 , 直接從連結清單中取出即可 . LookasideList 由 OS 實作 , 系統有個 Timer 回調 , 會不斷的取檢查其連結清單深度和每個 Pool 片段的命中率 , 當發現命中率很低時 , 系統會回收這些片段 . 是以 , 仍然會造成大量記憶體碎片 . -- 是以, 建議頻繁但不量大時, 使用LookasideList是個好主意. 又頻繁又量大時, 就要慎重了.
由于在 DiskFilter 層幾乎不允許有失敗的情況 , 是以 , 目前比較投機的解決辦法是反複重試 , 直到其不傳回為空為止 .
比較徹底的解決辦法 : 自己動手 , 豐衣足食 . 自己管理記憶體 . 這樣 , 不會造成大量碎片 , 而且 , 效率相對較高 . 相對而言 ExAllocatePool 實際上的開銷是比較大的 .
在測試過程中 , 還發現一些現象 , 算與這個問題有關 , 分享給大家 :
1 絕不要低估系統同時發下來的 IRP 的數量 (NTFS 是量大 )
2 絕不要低估系統 IRP 請求的長度 (FAT32 是單個 IRP 很大 , 不過還好 , 還沒有遇到超過 64M 的 )