MDL的理解

来源:互联网 发布:晨讯资源网源码 编辑:程序博客网 时间:2024/05/18 13:23

最近在看《Windows NT File System Internals》,重新理解MDL。

什么是mdl:    

         Mdl的全称是内存描述符列表(Memory Descriptor list),主要用于关联虚拟地址和物理地址,有点像页表。但是,比页表简单,其实它对于虚拟地址的映射也是查看页表得到的。虚拟地址在mdl中可以通过一个宏获得,物理地址的页帧存放在紧接着mdl的内存当中。(Windows没给出)。Mdl在内存的作用很大,在驱动的数据传输和缓冲管理,页错误等都有应用。

 

 

MDL在WRK中的描述:

 

// begin_ntddkbegin_wdm begin_ntifs begin_ntosp begin_ntndis

 

//

// I/O systemdefinitions.

//

// Define a MemoryDescriptor List (MDL)

//

// An MDL describespages in a virtual buffer in terms of physical pages.  The

// pages associatedwith the buffer are described in an array that is allocated

// just after theMDL header structure itself.

//

// One simplycalculates the base of the array by adding one to the base

// MDL pointer:

//

//      Pages = (PPFN_NUMBER) (Mdl + 1);

//

// Notice thatwhile in the context of the subject thread, the base virtual

// address of abuffer mapped by an MDL may be referenced using the following:

//

//      Mdl->StartVa | Mdl->ByteOffset

//

 

 

在MDL头后的一个数组才是真正存放着物理地址的地方,这个可以通过(PPFN_NUMBER) (Mdl + 1)得到物理页帧。

typedef struct _MDL{

    struct _MDL *Next;

    CSHORT Size;

    CSHORT MdlFlags;

    struct _EPROCESS *Process;

    PVOID MappedSystemVa;

    PVOID StartVa;

    ULONG ByteCount;

    ULONG ByteOffset;

} MDL, *PMDL;

 

最好不要直接访问MDL,而是使用windows提供的来处理。

MmGetMdlVirtualAddress 获取缓冲区的虚拟内存地址
MmGetMdlByteCount 获取缓冲区的大小(字节数)
MmGetMdlByteOffset 获取缓冲区开端的物理页的大小(字节数)
MmGetMdlPfnArray  获取记录物理页码的一个数组指针。

MmInitializeMdl   初始化Mdl

MmGetSystemAddressForMdlSafe

 

他们在WRK中的实现是这样的

#define MmGetMdlVirtualAddress(Mdl)                                     \

((PVOID) ((PCHAR) ((Mdl)->StartVa) +(Mdl)->ByteOffset))   这个比较常用,

 

#defineMmGetMdlByteCount(Mdl) ((Mdl)->ByteCount)

#defineMmGetMdlByteOffset(Mdl) ((Mdl)->ByteOffset)

#define MmGetMdlPfnArray(Mdl)((PPFN_NUMBER)(Mdl + 1))

 

#defineMmInitializeMdl(MemoryDescriptorList, BaseVa, 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); \

    }

 

#defineMmGetSystemAddressForMdlSafe(MDL, PRIORITY)                    \

     (((MDL)->MdlFlags &(MDL_MAPPED_TO_SYSTEM_VA |                   \

                       MDL_SOURCE_IS_NONPAGED_POOL)) ?               \

                            ((MDL)->MappedSystemVa) :                 \

                            (MmMapLockedPagesSpecifyCache((MDL),     \

                                                          KernelMode, \

                                                           MmCached,   \

                                                          NULL,       \

                                                          FALSE,      \

                                                          (PRIORITY))))

与MDL相关的函数:

IoAllocateMdl 分配MDL

先检验Length是不是大于2G,大于就返回。


首先确定MDL的大小,然后从Lookaside中分配,如果不行,就从非分页内存中分配。

其中分配的内存大小是从size来确定的。这个size是通过虚拟地址的后12位(也就是物理页内偏移)+length来确定的。但是他有个下限,也就是分配的MDL的大小必须是大于等于sizeof(MDL) +(sizeof(PFN_NUMBER) *IOP_FIXED_SIZE_MDL_PFNS); 其中IOP_FIXED_SIZE_MDL_PFNS是23.也就是说在MDL后面至少保留了23个PFN_NUMBER结构.要注意fixedSize的大小可能是0可能是MDL_ALLOCATED_FIXED_SIZE(8)。

再调用函数MmInitializeMdl初始化MDL. MmInitializeMdl是个宏,它在WRK中是这样定义的:

最后把MDL加入到IRP的mdl链表末。

 

 

MmBuildMdlForNonPagedPool  

为非分页内存建立MDL并且关联PFN。调用这个函数后,真正的虚拟地址在mdl中的systemva中,他的process0,说明不属于任何进程,而再mdl后的pfn数组指向mdl里面的虚拟地址关联的物理地址的页帧号。这些页帧号是相连的。


得到字节的开始地址:虚拟地址+位移。得到页数目。(利用(物理偏移+字节数)/0x1000得到页数目)


virtualaddress得到物理地址页帧,如果virtualaddress就是物理地址,则直接从物理地址用windowsMI_CONVERT_PHYSICAL_TO_PFN得到PFN,然后把MDL后面的所有的PFN数组都填上;如果虚拟地址不是物理地址,则从页表中得到PFN,填入PFN数组。最后设置属性。

 

MmProbeAndLockPages

试探和锁定物理页面

这个函数分为2部分,一部分检查MDL,一部分锁定物理页。

先检查访问模式:


再检查每个虚拟地址的PEDPTEPFN


注意这里对下一个虚拟地址的计算和对下一个PFN PTE的计算

下一个虚拟地址 = 虚拟地址+一页大小,再把后面12位置零。

下一个PFN = page+1.这里很奇怪PTE一定是直接是PTE表的下一个表项.

 

然后是锁定页面



先保证在小于DIAPATCH_LEVEL中断级下获得Mmpfnlock。然后保证要锁定后锁定页面的总数(之前可能有锁定的页面)小于工作限定和锁定页面后小于可用工作集的大小。


设置MDL的锁标志,对于每个PDE PTE PFN进行检查,增加PFN database element计数。

 

MmMapLockedPagesSpecifyCache

映射物理地址到系统的虚拟地址或者用户空间的虚拟地址

 

 

上面的函数,IoAllocateMdl只是分配MDL,并设置他的结构,但是并没有对它后面的页帧进行设置。下面2个函数才是对他的页帧进行设置:MmBuildMdlForNonPagedPoolMmProbeAndLockPages



参考:WRK源码



原创粉丝点击