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/O,I/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了。其实出现这种情况,就是当驱动程序使用了一个MDL的IRP包发送给另外一个驱动程序,然后这个驱动程序又需要从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 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |