加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

ReactOS分析MDL实现

发布时间:2020-12-15 04:54:25 所属栏目:百科 来源:网络整理
导读:用户需要从文件里面读取一段数据,最终的读操作在内核层进行。但是最终内存需要返回到用户空间。如果读取操作比较慢就需要将内核内存空间中的数据复制到用户的内核空间中去。有没有一种办法避免这种读取操作呢?MDL完全可以处理这种问题,并且更进一步。 首

用户需要从文件里面读取一段数据,最终的读操作在内核层进行。但是最终内存需要返回到用户空间。如果读取操作比较慢就需要将内核内存空间中的数据复制到用户的内核空间中去。有没有一种办法避免这种读取操作呢?MDL完全可以处理这种问题,并且更进一步。

首先,看一下MDL的定义:

typedef struct _MDL {
  struct _MDL *Next;
  CSHORT Size;
  CSHORT MdlFlags;
  struct _EPROCESS *Process;
  PVOID MappedSystemVa;
  PVOID StartVa;
  ULONG ByteCount;
  ULONG ByteOffset;
} MDL,*PMDL;
typedef MDL *PMDLX;

第一个成员变量Next用于连接成一个MDL的链表,而第二个成员变量则用于保存MDL的大小,MDL的大小并不仅仅只是这个结构体的大小,还包含MDL管理的内存页地址指针的大小。第三个成员变量表明MDL管理的页的属性,第四个变量表明MDL管理的内存页属于哪一个进程,如果这个成员变量为NULL,则表明是从非换页内存中映射的。其余几个成员变量在函数实现中介绍。

首先看IoAllocateMdl函数,这个函数首先对内存按照内存页大小进行对齐,计算出需要多少内存页进行映射。如果需要进行映射的内存页的数量大于23,则从非换页内存中进行分配,否则将直接从全局的Lookaside列表中进行分配。对MDL的初始化是在一个宏当中完成的,主要填充MDL数据结构的一些成员变量。

#define MmInitializeMdl(_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; 
}

其中成员变量StartVa是页对齐的起始地址,该成员变量和下面的ByteOffset一起用于找到实际的起始地址。ByteOffset用作内存偏移。ByteCount用于保存需要映射的虚拟内存的长度。到这里,MDL的虚拟内存部分的信息基本就保存到MDL中了。当传递给IoAllocateMdl的参数Irp不为空,则需要将Mdl给链接到Irp当中。SecondaryBuffer参数为TRUE时,表明Irp已经存在MDL,新生成的MDL仅仅是Irp链表中的一部分。

由于传递给IoAllocateMdl函数的是系统非换页内存的虚拟地址,所以需要调用MmBuildMdlForNonPagedPool函数来获得虚拟页面的物理内存,也就是说两者之间有对应关系。由于非换页内存是系统内存,所以MDL的成员变量Process设置为NULL,同时成员变量MappedSystemVa等于传递给IoAllocateMdl的虚拟地址,这个成员变量实际是将原来的虚拟内存地址映射到内核后的虚拟起始地址。接下来需要通过虚拟地址得到物理地址,首先通过MiAddressToPte宏从虚拟地址找到相应的页表入口。

#define MiAddressToPte(x) 
((PMMPTE)(((((ULONG)(x)) >> 12) << 2) + PAGETABLE_MAP))

首先右移12位得到页表项移除的是内存页内部的偏移(2的12次方正好等于一个内存页的大小——4K)然后右移4位得到页表的索引——每一个页表索引占用4个字节。经过上面的步骤,就由虚拟地址得到物理页面的相关信息。再经由宏PFN_FROM_PTE得到物理页的页号。

#define PFN_FROM_PTE(v) ((v)->u.Hard.PageFrameNumber)

如果需要使用MDL管理的内存,可以利用MmGetSystemAddressForMdlSafe获取重新映射后的虚拟地址。

#define MmGetSystemAddressForMdlSafe(_Mdl,_Priority) 
  (((_Mdl)->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA 
    | MDL_SOURCE_IS_NONPAGED_POOL)) ? 
    (_Mdl)->MappedSystemVa : 
    (PVOID) MmMapLockedPagesSpecifyCache((_Mdl),
      KernelMode,MmCached,NULL,FALSE,(_Priority)))

如果是换页内存,则处理和上面的流程大体相似,最主要的差别是对虚拟内存和物理内存之间的映射和MmGetSystemAddressForMdlSafe返回的值不一样。在换页内存中则利用MmProbeAndLockPages函数将换页内存给锁定,函数MmProbeAndLockPages首先分析传递进来的换页内存是否可用,如果不可用则人为制造缺页中断将物理页面和虚拟页面映射到一起。另外还需要检测当需要的权限不是IoReadAccess时,需要判断当前页是否可写,如果不可写同时也不是写时复制,则直接发出内存访问异常。在虚拟内存和物理内存建立映射之后,同样需要利用宏PFN_FROM_PTE将虚拟内存页转化为物理页号,然后以物理页号为参数调用MiGetPfnEntry函数得到物理页的相关信息。如果相关的物理页信息不为0,则表明是内存空间,否则为IO端口地址。下面的代码部分是在内存物理页面中的处理过程。

            InterlockedExchangeAddSizeT(&MmSystemLockPagesCount,1);
            do
            {
                OldRefCount = Pfn1->u3.e2.ReferenceCount;
                RefCount = InterlockedCompareExchange16((PSHORT)&Pfn1->u3.e2.ReferenceCount,OldRefCount + 1,OldRefCount);
            } while (OldRefCount != RefCount);
            if (OldRefCount != 1)
            {
                InterlockedExchangeAddSizeT(&MmSystemLockPagesCount,-1);
            }

接下来是调用MmMapLockedPagesSpecifyCache将上面的物理页与系统的虚拟页号建立对应关系。MmMapLockedPagesSpecifyCache函数调用MiReserveSystemPtes函数得到系统保留的虚拟页号,然后通过物理页号监理一个可用的虚拟页号。并将其实的虚拟页号赋值给MDL的成员变量MappedSystemVa。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读