使用 MDL

跨越一系列連續虛擬記憶體位址的 I/O 緩衝區可以分散到數個實體頁面,而且這些頁面可以是不連續的。 作業系統會使用 記憶體描述項清單 (MDL) 來描述虛擬記憶體緩衝區的實體頁面配置。

MDL 是由 MDL 結構所組成,後面接著描述 I/O 緩衝區所在實體記憶體的資料陣列。 MDL 的大小會根據 MDL 描述的 I/O 緩衝區特性而有所不同。 系統常式可用來計算 MDL 的必要大小,以及配置和釋放 MDL。

MDL 結構是半不透明。 您的驅動程式應該只直接存取這個 結構的 NextMdlFlags 成員。 如需使用這兩個成員的程式碼範例,請參閱下列範例一節。

MDL 的其餘成員不透明。 請勿直接存取 MDL 的不透明成員。 請改用下列作業系統提供的宏,在 結構上執行基本作業:

MmGetMdlVirtualAddress 會傳回 MDL 所描述之 I/O 緩衝區的虛擬記憶體位址。

MmGetMdlByteCount 會傳回 I/O 緩衝區的大小,以位元組為單位。

MmGetMdlByteOffset 會傳回 I/O 緩衝區開頭實體頁面內的位移。

您可以使用 IoAllocateMdl 常式來配置 MDL。 若要釋放 MDL,請使用 IoFreeMdl 常式。 或者,您可以配置非分頁式記憶體的區塊,然後呼叫 MmInitializeMdl 常式,將此記憶體區塊格式化為 MDL。

IoAllocateMdlMmInitializeMdl都不會初始化緊接在 MDL 結構之後的資料陣列。 對於位於非分頁式記憶體之驅動程式配置區塊中的 MDL,請使用 MmBuildMdlForNonPagedPool 初始化此陣列,以描述 I/O 緩衝區所在的實體記憶體。

對於可分頁記憶體,虛擬和實體記憶體之間的對應是暫時性的,因此在 MDL 結構後面的資料陣列只有在特定情況下才有效。 呼叫 MmProbeAndLockPages 以將可分頁記憶體鎖定為就地,並初始化目前版面配置的這個資料陣列。 在呼叫端使用 MmUnlockPages 常式之前,記憶體將不會分頁,此時資料陣列的內容不再有效。

MmGetSystemAddressForMdlSafe常式會將指定的 MDL 所描述的實體頁面對應至系統位址空間中的虛擬位址,如果它們尚未對應至系統位址空間。 此虛擬位址對於可能必須查看頁面以執行 I/O 的驅動程式很有用,因為原始虛擬位址可能是只能在其原始內容中使用的使用者位址,而且可以隨時刪除。

請注意,當您使用 IoBuildPartialMdl 常式建置部分 MDL 時, MmGetMdlVirtualAddress 會傳回部分 MDL 的原始起始位址。 如果 MDL 最初建立為使用者模式要求的結果,則此位址是使用者模式位址。 因此,位址在要求來源的進程內容之外沒有相關性。

驅動程式通常會藉由呼叫MmGetSystemAddressForMdlSafe宏來對應部分 MDL 來建立系統模式位址。 這可確保驅動程式可以繼續安全地存取頁面,而不論進程內容為何。

當驅動程式呼叫IoAllocateMdl時,可以將 IRP 與新配置的 MDL 產生關聯,方法是將 IRP 的指標指定為IoAllocateMdlIrp參數。 IRP 可以有一或多個相關聯的 MDL。 如果 IRP 有與其相關聯的單一 MDL,IRP 的 MdlAddress 成員會指向該 MDL。 如果 IRP 有多個相關聯的 MDL, MdlAddress 會指向與 IRP 相關聯之 MDL 連結清單中的第一個 MDL,稱為 MDL 鏈結。 MDL 是由其 Next 成員連結。 鏈結中最後一個 MDL 的 Next 成員會設定為 Null

如果驅動程式呼叫IoAllocateMdl時,它會指定SecondaryBuffer參數的FALSE,IRP 的MdlAddress成員會設定為指向新的 MDL。 如果 SecondaryBufferTRUE,常式會在 MDL 鏈結結尾插入新的 MDL。

完成 IRP 時,系統會解除鎖定並釋放與 IRP 相關聯的所有 MDL。 系統會先解除鎖定 MDL,再將 I/O 完成常式排入佇列,並在 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 要求的來源,您的驅動程式必須在 I/O 要求完成時清除 IRP 和附加至 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 時自動取消對應頁面。