使用 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 的指针指定为 IoAllocateMdlIrp 参数,将 IRP 与新分配的 MDL 相关联。 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 链。 驱动程序可以通过更新 “下一步 ”成员来手动将 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 描述的物理页面,则示例函数在调用 IoFreeMdl 以释放 MDL 之前调用 MmUnlockPages 例程来解锁页面。 但是,示例函数在调用 IoFreeMdl 之前不需要显式取消映射页面。 相反, IoFreeMdl 会在释放 MDL 时自动取消映射页面。