IoAllocateMdl,MmProbeAndLockPages的用法

IoAllocateMdl,MmProbeAndLockPages的用法  

2012-02-08 10:51:20|  分类: Windows内核编程 |  标签: |字号 订阅

第一,MDL的一个用法是提供驱动程序访问用户模式数据缓冲区的一种方式:直接I/O。也就是说通过MDL告诉驱动程序如何访问用户模式的数据缓冲区,这很好理解;
第二,第二个用法是这样的,一些驱动程序在执行直接 I/O 来满足设备 I/O 控制请求时也使用 MDL,常用的方式如下:
1,分配一个buf,可能是分页或者非分页的;
2,调用IoAllocateMdl,指向这个buf;
3,对于分页内存来说,调用MmProbeAndLockPages以及MmGetSystemAddressForMdlSafe来锁定内存页,以防止被page out出去;
  对于非分页内存而言,调用MmBuildMdlForNonPagedPool映射到物理内存上。

对于第二个用法,对于分页内存可能还有意义,保证其不被page out,但对于非分页内存,我觉得除了DMA以外,别的都没有用处,既然已经是非分页内存了,直接使用就好了,为什么还要生成一个MDL去做映射?我一直都不是非常理解,有可能根本就是理解错误,请大家指教

IoAllocateMdl

 IoAllocateMdl 函数分配足够映射一块缓存的MDL,给定缓存的起始地址和长度.

PMDL
IoAllocateMdl(
    __in_opt PVOID
VirtualAddress,
    __in ULONG
Length,
    __in BOOLEAN
SecondaryBuffer,
    __in BOOLEAN
ChargeQuota,
    __inout_opt PIRP
Irp OPTIONAL
    );

驱动可以使用IoAllocateMdl 函数实现大缓存分片的目的,通过一个独立的MDL来映射缓存的一小部分,或者映射驱动分配的内存.驱动会调用MmBuildMdlForNonPagedPool 来设置MDL的内存,使得MDL描述驱动分配的缓存处于不可置换的内存中。

 Length参数指明了MDL指向的缓存的大小。在Windows Server 2003, Windows XP, 和Windows 2000, 能够分配的最大缓存字节数为PAGE_SIZE * (65535 - sizeof(MDL)) / sizeof(ULONG_PTR)。在Windows Vista和Windows Server 2008, 最大缓存字节数是(2GB - PAGE_SIZE).在Windows 7和Windows Server 2008 R2中, 最大缓存字节数是(4GB - PAGE_SIZE).

   如果SecondaryBuffer参数是FALSE, 这个函数会更新Irp->MdlAddress ,让它指向一个新的MDL, 也就是说让它支持大缓存分片吧。 如果SecondaryBuffer为TRUE, 这个函数把MDL添加到Irp->MdlAddress指向的链之后。

MDL(memory descriptor list)

http://hi.baidu.com/reversefish/blog

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。

取得MDL的虚拟内存位置。 DDK特别讲明,Lower-Level Driver不可以直接把这个Address拿来使用,因为这有可能是user-space的内存位置。因此,Driver必须呼叫MmGetSystemAddressForMdlSafe()来取得并锁定这个Address所对应到的system-space的内存位置。

如果Driver希望建立的MDL是映射到Driver自己配置的Non-Paged记忆体的话,Driver还得呼叫MmBuildMdlForNonPagedPool()。这是因为IoAllocateMdl()只有配置记忆体,但是并没有Build MDL

临时为用户空间缓冲区增添一个系统空间映射,这使同一组物理页面有了两个虚拟地址区间,其一就是原来的用户空间虚拟地址区间,其二则是系统空间的虚拟地址区间。于是,就可以通过系统空间的虚拟地址访问用户空间缓冲区了,直到完成操作而返回用户空间时才撤销系统空间的映射。这种方法称为“直接”方法。直接方法对于很小的缓冲区是不划算的,因为临时映射的建立和撤销需要一定的开销,对于大一点的缓冲区才合适。

1.用户态到内核态

BaseAddr = OpenKernel32();        //映射kernel32的section到本进程的低2G空间
if (!BaseAddr)
{
KdPrint(("DriverEntry--OpenKernel32 failure!\n"));
return 0;
}

KdPrint(("BaseAddr: 0x%08x\n",BaseAddr));

//创建一个MDL
pMDL = IoAllocateMdl(BaseAddr,0x11c000,FALSE,FALSE,NULL);
if (!pMDL)
{
KdPrint(("pMDL == NULL\n"));
return 0;
}

_try
{
MmProbeAndLockPages(pMDL,UserMode,IoReadAccess);

}
_except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("MmProbeAndLockPages exception\n"));
}

_try
{
pMapedAddr = MmMapLockedPagesSpecifyCache(pMDL,KernelMode,MmCached,NULL,FALSE,NormalPagePriority);
if (!pMapedAddr)
{
KdPrint(("pMapedAdd == NULL\n"));
return 0;
}

}
_except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("MmMapLockedPagesSpecifyCache exception\n"));
}

2.内核态到用户态

pShareMM_SYS=ExAllocatePool(NonPagedPool,1024);//size必须是page的整数倍
RtlZeroMemory(pShareMM_SYS,1024);

pShareMM_MDL=IoAllocateMdl(pShareMM_SYS,1024,FALSE,FALSE,NULL);
MmBuildMdlForNonPagedPool(pShareMM_MDL);

pShareMM_User=MmMapLockedPagesSpecifyCache(pShareMM_MDL,UserMode,MmCached,NULL,FALSE,NormalPagePriority);
KdPrint(("pShareMM_SYS的地址为: 0x%p\n",(PUCHAR)pShareMM_SYS));
KdPrint(("pShareMM_User的地址为: 0x%p\n",(PUCHAR)pShareMM_User));

展开阅读全文

没有更多推荐了,返回首页