reactos操作系统实现(85)

内核里也需要访问用户应用程序内存,那么有什么方法呢?在ReactOS主要有两种方法:一种是使用缓冲I/O的方法,在驱动程序运行前,I/O管理器把写数据复制到这个缓冲区,并在请求完成时把读数据复制回到用户空间;另一种是使用直接I/O,这是优先的技术,因为它减少数据复制。这是通过I/O管理器传递一个内存描述符列表(MDL-- Memory descriptor list)来实现的,这个描述符列表是描述用户空间缓冲区。

MDL的实现代码如下:

#001  PMDL

#002  NTAPI

#003  IoAllocateMdl(IN PVOID VirtualAddress,

#004                IN ULONG Length,

#005                IN BOOLEAN SecondaryBuffer,

#006                IN BOOLEAN ChargeQuota,

#007                IN PIRP Irp)

#008  {

函数IoAllocateMdl前两个参数定义了虚拟地址和内存区的大小,是建立MDL所必须的。如果MDL不与IRP相关联,则第三个参数就为FALSE。第四个参数定义是否需要减少进程的份额,并只用于位于驱动程序链最上层的驱动程序或是单层的驱动程序(。每一个进程都要获得一定份额的系统资源。当进程为自己分配资源时,这个份额就会减小。如果份额用完,就不能再为其分配相应的资源。最后一个参数定义了一个非必要的指向IRP的指针,通过这个指针MDL可以与IRP关联。例如,对于直接I/OI/O管理器为用户缓冲区建立MDL,并将其地址送至IRP.MdlAddress

 

#009      PMDL Mdl = NULL, p;

#010      ULONG Flags = 0;

#011      ULONG Size;

#012 

 

如果申请的内存超过2G,就返回失败。

#013      /* Fail if allocation is over 2GB */

#014      if (Length & 0x80000000) return NULL;

#015 

 

计算需要使用多少页内存。

#016      /* Calculate the number of pages for the allocation */

#017      Size = ADDRESS_AND_SIZE_TO_SPAN_PAGES(VirtualAddress, Length);

#018      if (Size > 23)

#019      {

 

大于23页,就计算实际使用的大小页。

#020          /* This is bigger then our fixed-size MDLs. Calculate real size */

#021          Size *= sizeof(PFN_NUMBER);

#022          Size += sizeof(MDL);

#023          if (Size > MAXUSHORT) return NULL;

#024      }

#025      else

#026      {

如果小于等于23页,就直接使用23页的大小。

#027          /* Use an internal fixed MDL size */

#028          Size = (23 * sizeof(PFN_NUMBER)) + sizeof(MDL);

#029          Flags |= MDL_ALLOCATED_FIXED_SIZE;

#030 

 

从后备列表里找到合适的内存。

#031          /* Allocate one from the lookaside list */

#032          Mdl = IopAllocateMdlFromLookaside(LookasideMdlList);

#033      }

#034 

 

如果前面没有找到相应的MDL内存,就重新分配一个。

#035      /* Check if we don't have an mdl yet */

#036      if (!Mdl)

#037      {

#038          /* Allocate one from pool */

#039          Mdl = ExAllocatePoolWithTag(NonPagedPool, Size, TAG_MDL);

#040          if (!Mdl) return NULL;

#041      }

#042 

 

通过内存管理器的函数MmInitializeMdl初始化MDL

#043      /* Initialize it */

#044      MmInitializeMdl(Mdl, VirtualAddress, Length);

#045      Mdl->MdlFlags |= Flags;

#046 

 

检查IRP是否存在,如果存在就把MDL的地址放到IRP包里。

#047      /* Check if an IRP was given too */

#048      if (Irp)

#049      {

#050          /* Check if it came with a secondary buffer */

#051          if (SecondaryBuffer)

#052          {

#053              /* Insert the MDL at the end */

#054              p = Irp->MdlAddress;

#055              while (p->Next) p = p->Next;

#056              p->Next = Mdl;

#057        }

#058        else

#059        {

#060              /* Otherwise, insert it directly */

#061              Irp->MdlAddress = Mdl;

#062        }

#063     }

#064 

 

最后返回MDL的地址。

#065      /* Return the allocated mdl */

#066      return Mdl;

#067  }

 

 

MDL描述符表里,还有这样的需求,当作一个MDL表已经映射过一次MDL了,那么当用户再次想去分配这个MDL时,就需要使用函数IoBuildPartialMdl来再次映射MDL了。其实出现这种情况,就是当驱动程序使用了一个MDLIRP包发送给另外一个驱动程序,然后这个驱动程序又需要从IRP包里的MDL再分配一个MDL出来。这个函数的实现代码如下:

#001  /*

#002   * @implemented

#003   */

#004  VOID

#005  NTAPI

#006  IoBuildPartialMdl(IN PMDL SourceMdl,

#007                    IN PMDL TargetMdl,

#008                    IN PVOID VirtualAddress,

#009                    IN ULONG Length)

#010  {

 

取目标MDL地址。

#011      PPFN_NUMBER TargetPages = (PPFN_NUMBER)(TargetMdl + 1);

 

取源MDL地址。

#012      PPFN_NUMBER SourcePages = (PPFN_NUMBER)(SourceMdl + 1);

#013      ULONG Offset;

#014      ULONG FlagsMask = (MDL_IO_PAGE_READ |

#015                         MDL_SOURCE_IS_NONPAGED_POOL |

#016                         MDL_MAPPED_TO_SYSTEM_VA |

#017                         MDL_IO_SPACE);

#018 

 

计算偏移位置。

#019      /* Calculate the offset */

#020      Offset = (ULONG)((ULONG_PTR)VirtualAddress -

#021                       (ULONG_PTR)SourceMdl->StartVa) -

#022                       SourceMdl->ByteOffset;

#023 

 

计算源MDL是否有足够的长度。

#024      /* Check if we don't have a length and calculate it */

#025      if (!Length) Length = SourceMdl->ByteCount - Offset;

#026 

 

设置进程、虚拟地址和需要内存的大小。

#027      /* Write the process, start VA and byte data */

#028      TargetMdl->StartVa = (PVOID)PAGE_ROUND_DOWN(VirtualAddress);

#029      TargetMdl->Process = SourceMdl->Process;

#030      TargetMdl->ByteCount = Length;

#031      TargetMdl->ByteOffset = BYTE_OFFSET(VirtualAddress);

#032 

 

重新计算页面的空间。

#033      /* Recalculate the length in pages */

#034      Length = ADDRESS_AND_SIZE_TO_SPAN_PAGES(VirtualAddress, Length);

#035 

 

设置MDL的标志。

#036      /* Set the MDL Flags */

#037      TargetMdl->MdlFlags &= (MDL_ALLOCATED_FIXED_SIZE | MDL_ALLOCATED_MUST_SUCCEED);

#038      TargetMdl->MdlFlags |= SourceMdl->MdlFlags & FlagsMask;

#039      TargetMdl->MdlFlags |= MDL_PARTIAL;

#040 

#041      /* Set the mapped VA */

#042      TargetMdl->MappedSystemVa = (PCHAR)SourceMdl->MappedSystemVa + Offset;

#043 

 

开始从源MDL里拷贝数据到新的MDL

#044      /* Now do the copy */

#045      Offset = ((ULONG_PTR)TargetMdl->StartVa - (ULONG_PTR)SourceMdl->StartVa) >>

#046               PAGE_SHIFT;

#047      SourcePages += Offset;

#048      RtlCopyMemory(TargetPages, SourcePages, Length * sizeof(PFN_NUMBER));

#049  }

 

前面都是创建MDL的,下面这个函数就是删除MDL所占用的资源,实现如下:

#001  VOID

#002  NTAPI

#003  IoFreeMdl(PMDL Mdl)

#004  {

 

让内存管理器删除所有MDL

#005      /* Tell Mm to reuse the MDL */

#006      MmPrepareMdlForReuse(Mdl);

#007 

 

检查是否重新分配的内存,如果是就需要删除掉,否则就放回到后备列表,以便下一次使用。

#008      /* Check if this was a pool allocation */

#009      if (!(Mdl->MdlFlags & MDL_ALLOCATED_FIXED_SIZE))

#010      {

#011          /* Free it from the pool */

#012          ExFreePoolWithTag(Mdl, TAG_MDL);

#013      }

#014      else

#015      {

#016          /* Free it from the lookaside */

#017          IopFreeMdlFromLookaside(Mdl, LookasideMdlList);

#018      }

#019  }

#020  

你可能感兴趣的:(react)