MmProbeAndLockPages 到底锁定什么?
驱动程序有时候需要锁定内存页以使得它们在特定的操作期间驻留在内存中,例如在 DPC 例程中将数据从设备复制到数据缓冲区或者对缓冲区进行 DMA。MmProbeAndLockPages 例程使得一个指定的内存范围驻留(如果尚未驻留),确认内存页允许以指定的访问模式执行指定的操作,并锁定内存中的页以使得它们不能被页交换出去。
MmProbeAndLockPages 通过在页帧号 (page frame number) 数据库(描述物理内存中每个页的状态)中的页帧号(PFN)条目上增加引用计数实现这个功能。只要 PFN 引用计数非零,物理页就不会被重用。
但是,不要混淆物理页及其虚拟地址,这一点非常重要。MmProbeAndLockPages 在内存中锁定物理页,但是如果相关的工作集被修改(或者如果进程释放或取消映射地址范围),那么它们的虚拟地址会变得无效。即使可以保证后备物理页保持驻留,虚拟地址映射也没有保证。驱动程序中这种情况的一个症状是 MmIsAddressValid 将指示虚拟地址页故障,认为内存已经被页交换出去。如果驱动程序试图使用虚拟地址(假设地址范围未被删除),那么它会遇到与试图使用可分页内存时相同的问题:如果驱动程序以低于 DISPATCH_LEVEL 的级别运行,那么系统可以指示页故障;如果驱动程序在以 DISPATCH_LEVEL 或高于 DISPATCH_LEVEL 的级别运行,那么系统将进行错误检查。(关于 Windows 如何管理虚拟内存和物理内存的深入讨论,请参见 Inside Windows 2000(第三版)的第 7 章。)
如果在 DISPATCH_LEVEL 或高于 DISPATCH_LEVEL 的级别上需要虚拟地址,或者在另一个进程上下文中需要虚拟地址,那么驱动程序必须使用 MmGetSystemAddressForMdlSafe 映射 MDL 来获取 MDL 的未经修改的系统虚拟地址。这个虚拟地址从未被插入到工作集中,因此它不能被删除。不管线程或 IRQL 的进程上下文(在其中引用地址)如何,它都能保证正常工作。
您应该做什么?
-
调用 MmProbeAndLockPages 在内存中锁定 MDL 描述的页。始终应该在 try/except 块中执行此操作,因为如果 MmProbeAndLockPages 失败,将抛出一个异常。
-
要将锁定的物理页映射到系统空间,请使用 MDL 的指针来调用 MmGetSystemAddressForMdlSafe。(为必须在 Windows 98 上运行的驱动程序使用 MmGetSystemAddressForMdl。)为了避免浪费系统资源,只有在确实需要使用虚拟地址来访问内存页时才进行这种操作。
-
使用 MmGetSystemAddressForMdlSafe 返回的系统地址来通过虚拟地址访问锁定的页。
-
当您不再需要 MDL 描述的页时,请调用 MmUnlockPages 将它们解除锁定,然后调用 IoFreeMdl 来释放它们。
下列来自 Windows DDK(%winddk%/src/general/ioctl/sys/sioctl.c)中的系统 IOCTL 例子代码片段展示了如何进行这种操作:
- mdl = IoAllocateMdl(inBuf, inBufLength, FALSE, TRUE, NULL);
- if(!mdl)
- {
- ntStatus = STATUS_INSUFFICIENT_RESOURCES;
- break;
- }
-
- try
- {
-
- //
- // Probe and lock the pages of this buffer in physical memory.
- // You can specify IoReadAccess, IoWriteAccess or IoModifyAccess
- // Always perform this operation in a try except block.
- // MmProbeAndLockPages will raise an exception if it fails.
- //
- MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
- }
- except(EXCEPTION_EXECUTE_HANDLER)
- {
-
- ntStatus = GetExceptionCode();
- SIOCTL_KDPRINT((
- "Exception while locking inBuf 0X%08X in METHOD_NEITHER/n",
- ntStatus));
- IoFreeMdl(mdl);
- break;
- }
-
- //
- // Map the physical pages described by the MDL into system space.
- // Note: double mapping the buffer this way causes lot of
- // system overhead for large size buffers.
- //
-
- buffer = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority );
-
- if(!buffer) {
- ntStatus = STATUS_INSUFFICIENT_RESOURCES;
- MmUnlockPages(mdl);
- IoFreeMdl(mdl);
- break;
- }
-
- //
- // Now you can safely read the data from the buffer.
- //
- SIOCTL_KDPRINT(("/tData from User (SystemAddress) : "));
- PrintChars(buffer, inBufLength);
-
- //
- // Once the read is over unmap and unlock the pages.
- //
-
- MmUnlockPages(mdl);
- IoFreeMdl(mdl);