使用 MDL
跨一系列连续虚拟内存地址的 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 的指针指定为 IoAllocateMdl 的 Irp 参数,将 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。 如果 SecondaryBuffer 为 TRUE,则例程会在 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 时自动取消映射页面。