跨越一系列連續虛擬記憶體位址的 I/O 緩衝區可以分散到數個實體頁面,而且這些頁面可能會不和諧。 作系統會使用 記憶體描述元清單 (MDL) 來描述虛擬記憶體緩衝區的實體頁面配置。
MDL 是由 MDL 結構所組成,後面接著描述 I/O 緩衝區所在物理記憶體的數據數位。 MDL 的大小會根據 MDL 描述的 I/O 緩衝區特性而有所不同。 系統例程可用來計算 MDL 的必要大小,以及配置和釋放 MDL。
MDL 結構為半不透明。 您的驅動程序應該只直接存取這個結構的 Next 和 MdlFlags 成員。 如需使用這兩個成員的程式碼範例,請參閱下列範例一節。
MDL 的其餘成員不透明。 不可直接存取 MDL 的不透明成員。 請改用作業系統提供的下列巨集,以在結構上執行基本作業:
MmGetMdlVirtualAddress 會傳回 MDL 所描述之 I/O 緩衝區的虛擬記憶體位址。
MmGetMdlByteCount 會傳回 I/O 緩衝區的大小,以位元組為單位。
MmGetMdlByteOffset 會傳回 I/O 緩衝區開頭實體頁面內的位移。
您可以使用 IoAllocateMdl 例程來設定 MDL。 若要釋放 MDL,請使用 IoFreeMdl 例程。 或者,您可以藉由呼叫 MmInitializeMdl 例程來配置非分頁記憶體區塊,然後將此記憶體區塊格式化為 MDL。
IoAllocateMdl 和 MmInitializeMdl 都不會初始化緊接在 MDL 結構之後的數據陣列。 對於位於非分頁記憶體中,由驅動程式分配的區塊中的 MDL,請使用 MmBuildMdlForNonPagedPool 來初始化此陣列,以描述 I/O 緩衝區所在的物理記憶體。
對於可分頁記憶體,虛擬和物理記憶體之間的對應是暫時的,因此遵循 MDL 結構的數據陣列只有在某些情況下才有效。 呼叫 MmProbeAndLockPages 以鎖定可分頁記憶體到位,並初始化目前配置的數據陣列。 在呼叫端使用 MmUnlockPages 例程之前,記憶體將不會分頁,此時數據陣列的內容不再有效。
MmGetSystemAddressForMdlSafe 例程會將指定的 MDL 所描述的實體頁面對應至系統地址空間中的虛擬位址,如果它們尚未對應至系統地址空間。 此虛擬位址對於可能必須查看頁面以執行 I/O 的驅動程式很有用,因為原始虛擬位址可能是用戶位址,只能用於其原始內容,而且可以隨時刪除。
請注意,當您使用 IoBuildPartialMdl 例程建置部分 MDL 時, MmGetMdlVirtualAddress 會傳回部分 MDL 的原始起始位址。 如果 MDL 原本是使用者模式要求的結果所建立,則此位址是使用者模式位址。 因此,位址在要求來源的程序內容之外沒有相關性。
一般而言,驅動程式會呼叫 MmGetSystemAddressForMdlSafe 巨集來對應部分 MDL,從而建立系統模式位址。 這可確保不論進程內容為何,驅動程式都能繼續安全地存取頁面。
當驅動程式呼叫 IoAllocateMdl時,可以將IRP與新配置的 MDL 產生關聯,方法是將 IRP 的指標指定為 IoAllocateMdl的 Irp 參數。 IRP 可以與一個或多個 MDL 相關聯。 如果 IRP 有與其相關聯的單一 MDL,IRP 的 MdlAddress 成員會指向該 MDL。 如果 IRP 有多個相關聯的 MDL,MdlAddress 會指向與 IRP 相關聯之 MDL 連結清單中的第一個 MDL,稱為 MDL 鏈結。 MDLs 透過其 Next 成員進行連結。 鏈結中最後一個 MDL 的 Next 成員會設定為 NULL。
如果驅動程式呼叫IoAllocateMdl時,它會指定 SecondaryBuffer 參數的 FALSE,IRP 的 MdlAddress 成員會設定為指向新的 MDL。 如果 SecondaryBuffer 為 TRUE,例程會在 MDL 鏈結的結尾插入新的 MDL。
當 IRP 完成時,系統會解除鎖定並釋放與 IRP 相關聯的所有 MDL。 系統會在將 I/O 完成例程排入佇列之前解除鎖定 MDL,並在 I/O 完成例程執行之後釋放它們。
驅動程式可以使用每個 MDL 的 Next 成員來周遊 MDL 鏈結,以存取鏈結中的下一個 MDL。 驅動程式可以藉由更新 Next 成員,手動將 MDL 插入鏈結。
MDL 鏈結通常用來管理與單一 I/O 要求相關聯的緩衝區陣列。 (例如,網路驅動程式可以在網路作業中針對每個IP封包使用一個緩衝區。陣列中的每個緩衝區在鏈結中都有自己的 MDL。 當驅動程式完成要求時,它會將緩衝區合併成單一大型緩衝區。 系統接著會自動清除要求的所有已配置 MDL。
I/O 管理員是 I/O 要求的常見來源。 當 I/O 管理員完成 I/O 要求時,I/O 管理員會釋放 IRP,並釋放連結至 IRP 的任何 MDL。 其中某些 MDL 可能已由位於裝置堆疊中 I/O 管理員下方的驅動程式連結至 IRP。 同樣地,如果您的驅動程式是 I/O 要求的來源,您的驅動程式必須清除 IRP,以及 I/O 要求完成時附加至 IRP 的任何 MDL。
範例
下列程式代碼範例是驅動程序實作的函式,可從 IRP 釋放 MDL 鏈結:
VOID MyFreeMdl(PMDL Mdl)
{
PMDL currentMdl, nextMdl;
for (currentMdl = Mdl; currentMdl != NULL; currentMdl = nextMdl)
{
nextMdl = currentMdl->Next;
if (currentMdl->MdlFlags & MDL_PAGES_LOCKED)
{
MmUnlockPages(currentMdl);
}
IoFreeMdl(currentMdl);
}
}
如果鏈結中 MDL 所描述的實體頁面已鎖定,此範例函式會呼叫 MmUnlockPages 例程,在呼叫 IoFreeMdl 以釋放 MDL 之前解除鎖定頁面。 不過,範例函式不需要在呼叫 IoFreeMdl 之前明確取消對應頁面。 相反地,IoFreeMdl 會在釋放 MDL 時自動取消對應頁面。