MDL (memory descriptor list)
typedef struct _MDL {struct _MDL *Next;CSHORT Size;CSHORT MdlFlags;struct _EPROCESS *Process;PVOID MappedSystemVa;PVOID StartVa;ULONG ByteCount;ULONG ByteOffset; }MDL , *PMDL ;
MDL 只是一個對實體記憶體的描述,但是因為系統跟 Driver 都是使用虛擬記憶體,所以 MDL 就是把虛擬記憶體『映射』到實體記憶體(from DDK)。
這樣講是很模糊的,其實 MDL 的作用很簡單:當 Driver 要存取某個記憶體位置時,確保 MDL 所描述的記憶體位置不會引起 page fault。 為甚麼?因為『虛擬記憶體』的關係。Driver 存取的是虛擬記憶體,所以當系統丟給 Driver 一塊虛擬記憶體位置,而 Driver 如何知道這塊記憶體所代表的資料到底是在 RAM 還是 Disk(被 paged out) 內?
在 NDIS 裡面,Driver 是不需要去關心系統丟給 Driver 的記憶體到底是如何,反正一定是在 RAM 裡面就是了。但是在 I/O Control 內的 Direct I/O method 裡面, 這可不一定,因為這個記憶體位置可能是指到某個應用程式的虛擬記憶體,而應用程式的虛擬記憶體是非常有可能被 paged out。 所以 Driver 得呼叫 MmGetSystemAddressForMdlSafe() 來鎖定這塊記憶體(系統得把把資料由 Disk 載入到 RAM)。 寫個小程式就可以證明這一點:
PVOID buf = NULL;PMDL mdl = NULL;NDIS_STATUS ndis_status; ndis_status =NdisAllocateMemoryWithTag (&buf, 1000, 0x1111L); if (ndis_status != NDIS_STATUS_SUCCESS){DbgPrint ("NetVMini : Can not allocate test Memory for MDL.\n"); } else { mdl =IoAllocateMdl (buf, 1000, FALSE, FALSE, NULL); if (mdl != NULL) { MmBuildMdlForNonPagedPool(mdl);DbgPrint ("buf = 0x%p\n", buf);DbgPrint ("MDL.Size = 0x%X\n", mdl->Size);DbgPrint ("MDL.MdlFlags = 0x%X\n", mdl->MdlFlags);DbgPrint ("MDL.MappedSystemVa = 0x%p\n", mdl->MappedSystemVa);DbgPrint ("MDL.StartVa = 0x%p\n", mdl->StartVa);DbgPrint ("MDL.ByteCount = 0x%X\n", mdl->ByteCount);DbgPrint ("MDL.ByteOffset = 0x%X\n", mdl->ByteOffset);IoFreeMdl (mdl); mdl = NULL; }NdisFreeMemory (buf, 1000, 0); }
這個程式只是配置一塊 non-paged 記憶體,然後用一個 MDL 來描述它。 下面是程式的結果:
00000008 0.00103058 buf = 0x85322008 00000009 0.00104175 MDL.Size = 0x20 00000010 0.00104985 MDL.MdlFlags = 0xC 00000011 0.00106243 MDL.MappedSystemVa = 0x85322008 00000012 0.00107919 MDL.StartVa = 0x85322000 00000013 0.00109120 MDL.ByteCount = 0x3E8 00000014 0.00110321 MDL.ByteOffset = 0x8
buf = MappedSystemVa = StartVa + ByteOffset. Eureka!
看起來直接用 NdisAllocateMemoryWithTag() 所得到指標還比較好? MDL 還是有好處的啦,至少它可以串起一堆 MDL,當系統需要給 Driver 一堆記憶體區塊,而這些記憶體區塊全都是不連續的,那 MDL 就會很好用(NDIS_BUFFER 就是如此)。 而且在 NDIS 6.0 裡面,NET_BUFFER 只能使用 MDL。
跟 MDL 相關的函式有:
PMDL IoAllocateMdl ( INPVOID VirtualAddress, INULONG Length, INBOOLEAN SecondaryBuffer, INBOOLEAN ChargeQuota, IN OUTPIRP Irp OPTIONAL );
DDK 裡面特別強調,如果 Driver 希望建立的 MDL 是映射到 Driver 自己配置的 Non-Paged 記憶體的話,Driver 還得呼叫 MmBuildMdlForNonPagedPool()。 這是因為 IoAllocateMdl() 只有配置記憶體,但是並沒有 Build MDL(好模糊的說法)。 NDIS 6.0 有提供 NdisAllocateMdl(),它提供一氣呵成的服務。
VOID MmBuildMdlForNonPagedPool ( IN OUTPMDL MemoryDescriptorList );
The MmBuildMdlForNonPagedPool routine receives an MDL that specifies a virtual memory buffer in nonpaged pool, and updates it to describe the underlying physical pages. The MDL virtual address that is input must be within the nonpaged portion of system space, such as memory allocated by ExAllocatePoolWithTag with PoolType = NonPagedPool.
VOIDIoBuildPartialMdl ( IN PMDL SourceMdl, IN OUT PMDL TargetMdl, IN PVOID VirtualAddress, IN ULONG Length );
VOIDIoFreeMdl ( IN PMDL Mdl );
ULONGMmGetMdlByteCount (IN PMDL Mdl){ return Mdl->ByteCount; }
回傳這個 MDL 的大小。
ULONGMmGetMdlByteOffset (IN PMDL Mdl) { return Mdl->ByteOffset; }
回傳這個 MDL 離 StarVa 有多遠。
PVOIDMmGetMdlVirtualAddress (IN PMDL Mdl) { return ((PVOID) ((PCHAR) ((Mdl)->StartVa) + (Mdl)->ByteOffset)); }
取得 MDL 的虛擬記憶體位置。DDK 特別講明,Lower-Level Driver 不可以直接把這個 Address 拿來使用,因為這有可能是 user-space 的記憶體位置。因此,Driver 必須呼叫 MmGetSystemAddressForMdlSafe() 來取得並鎖定這個 Address 所對應到的 system-space 的記憶體位置。
PVOIDMmGetSystemAddressForMdlSafe (IN PMDL Mdl, IN MM_PAGE_PRIORITY Priority) { if (Mdl->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | MDL_SOURCE_IS_NONPAGED_POOL)) { return Mdl->MappedSystemVa; } else { return MmMapLockedPagesSpecifyCache(Mdl, KernelMode, MmCached, NULL, FALSE, Priority); } }
這個函式是個 Macro。程式碼很簡單,如果這個 MDL 已經被鎖定並映射,直接回傳 MappedSystemVa,否則就鎖定並映射它。
VOIDMmInitializeMdl (IN PMDL MemoryDescriptorList, IN PVOID BaseVa, IN SIZE_T Length){ MemoryDescriptorList->Next = (PMDL) NULL; MemoryDescriptorList->Size = (CSHORT)(sizeof(MDL) + sizeof(PFN_NUMBER) * ADDRESS_AND_SIZE_TO_SPAN_PAGES((BaseVa), (Length)))); MemoryDescriptorList->MdlFlags = 0; MemoryDescriptorList->StartVa = (PVOID) PAGE_ALIGN((BaseVa)); MemoryDescriptorList->ByteOffset = BYTE_OFFSET((BaseVa)); MemoryDescriptorList->ByteCount = (ULONG)(Length); }
這段程式碼已經對 MDL 說明的很清楚了。MDL 的目的是要串起多個分散的記憶體,而 MnInitializeMdl() 只是串起第一個記憶體位置。
VOIDMmPrepareMdlForReuse (IN PMDL MDL) { if ((MDL->MdlFlags & MDL_PARTIAL_HAS_BEEN_MAPPED) != 0) { MmUnmapLockedPages( MDL->MappedSystemVa, MDL ); } }
Very few drivers call this routine (DDK says).
VOIDMmProbeAndLockPages ( IN OUT PMDL MemoryDescriptorList, IN KPROCESSOR_MODE AccessMode, IN LOCK_OPERATION Operation );
這似乎是用在 Direct IO method 上。它會映射並鎖定 MDL所描述的虛擬記憶體。
ULONGMmSizeOfMdl ( IN PVOID Base, IN SIZE_T Length );
The MmSizeOfMdl routine returns the number of bytes to allocate for an MDL describing a given address range.
VOIDMmUnmapLockedPages ( IN PVOID BaseAddress, IN PMDL MemoryDescriptorList );
解除
No comments:
Post a Comment