1、系统调用NtAllocateVirtualMemory
凡是用户空间程序要求分配空间,无论是只要求预订一块虚拟地址区间,还是要求兑现预订的虚拟地址(映射到物理地址),或二者兼有,都可以NtAllocateVirtualMemory实现。
实际上,熟知的windowsAPI中的VirtualAlloc就是调用了此函数实现功能。
函数声明如下:
NTSTATUS NTAPI NtAllocateVirtualMemory(IN HANDLE ProcessHandle, IN OUT PVOID* UBaseAddress, IN ULONG_PTR ZeroBits, IN OUT PSIZE_T URegionSize, IN ULONG AllocationType, IN ULONG Protect)
参数说明:
①标示欲分配空间的进程。windows支持为其他进程分配空间。
②标示欲分配空间的基地址。
③如果参数2为0,则内核依据这个参数分配空间。此参数表示实际分配的地址必须以多少个0作为前导。例如,参数为10,则表示应分配在最低的4M之内。
④标示欲分配空间的最小长度
⑤标示分配类型的一组标示位。其中最重要的是MEM_RESERVE和MEM_COMMIT,前者标示预订虚拟空间,并不真正分配物理页面;后者标示分配物理页面。两者可以一起使用,标示预订与落实一次到位。
⑥标示对该区间的访问权限,例如:可读、可写、可执行等。
重要代码摘录:
NTSTATUS NTAPI NtAllocateVirtualMemory(IN HANDLE ProcessHandle, IN OUT PVOID* UBaseAddress, IN ULONG_PTR ZeroBits, IN OUT PSIZE_T URegionSize, IN ULONG AllocationType, IN ULONG Protect) { PEPROCESS Process; MEMORY_AREA* MemoryArea; ULONG_PTR MemoryAreaLength; ULONG Type; NTSTATUS Status; PMMSUPPORT AddressSpace; PVOID BaseAddress; ULONG RegionSize; PVOID PBaseAddress; ULONG PRegionSize; ULONG MemProtection; PHYSICAL_ADDRESS BoundaryAddressMultiple; KPROCESSOR_MODE PreviousMode; PAGED_CODE(); //参数合理性检查 ....... //获取给定进程的数据结构,类型:PEPROCESS Status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_VM_OPERATION, PsProcessType, PreviousMode, (PVOID*)(&Process), NULL); if (!NT_SUCCESS(Status)) { DPRINT("NtAllocateVirtualMemory() = %x/n",Status); return(Status); } Type = (AllocationType & MEM_COMMIT) ? MEM_COMMIT : MEM_RESERVE; DPRINT("Type %x/n", Type); //获取AVL树 AddressSpace = &Process->Vm; //需要互斥进行,使用互斥锁 MmLockAddressSpace(AddressSpace); if (PBaseAddress != 0) { //如果指定了基地址,则直接分配 MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, BaseAddress); if (MemoryArea != NULL) { MemoryAreaLength = (ULONG_PTR)MemoryArea->EndingAddress - (ULONG_PTR)MemoryArea->StartingAddress; if (MemoryArea->Type == MEMORY_AREA_VIRTUAL_MEMORY && MemoryAreaLength >= RegionSize) { //类型为普通虚存空间且大小合适,则根据参数改变目标区块的类型和属性 Status = MmAlterRegion(AddressSpace, MemoryArea->StartingAddress, &MemoryArea->Data.VirtualMemoryData.RegionListHead, BaseAddress, RegionSize, Type, Protect, MmModifyAttributes); //互斥操作结束 MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); /* Give the caller rounded BaseAddress and area length */ if (NT_SUCCESS(Status)) { *UBaseAddress = BaseAddress; *URegionSize = RegionSize; DPRINT("*UBaseAddress %x *URegionSize %x/n", BaseAddress, RegionSize); } //分配成功 return(Status); } else if (MemoryAreaLength >= RegionSize) { //长度也符合要求,但区间类型用于section /* Region list initialized? */ if (MemoryArea->Data.SectionData.RegionListHead.Flink) { //改变此区间的类型和保护模式 Status = MmAlterRegion(AddressSpace, MemoryArea->StartingAddress, &MemoryArea->Data.SectionData.RegionListHead, BaseAddress, RegionSize, Type, Protect, MmModifyAttributes); } else { Status = STATUS_ACCESS_VIOLATION; } MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); /* Give the caller rounded BaseAddress and area length */ if (NT_SUCCESS(Status)) { *UBaseAddress = BaseAddress; *URegionSize = RegionSize; DPRINT("*UBaseAddress %x *URegionSize %x/n", BaseAddress, RegionSize); } //分配成功 return(Status); } else { //区间长度不够,分配失败 MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); return(STATUS_UNSUCCESSFUL); } }//end if(MemoryArea != NULL) }//end if(PBaseAddress != 0) //PBaseAddress = 0,由内核分配一块大小合适的区间 Status = MmCreateMemoryArea(AddressSpace, MEMORY_AREA_VIRTUAL_MEMORY, &BaseAddress, RegionSize, Protect, &MemoryArea, PBaseAddress != 0, AllocationType & MEM_TOP_DOWN, BoundaryAddressMultiple); if (!NT_SUCCESS(Status)) { //失败 MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); return(Status); } MemoryAreaLength = (ULONG_PTR)MemoryArea->EndingAddress - (ULONG_PTR)MemoryArea->StartingAddress; //设置区块类型(刚建立的区间中的唯一区块) MmInitializeRegion(&MemoryArea->Data.VirtualMemoryData.RegionListHead, MemoryAreaLength, Type, Protect); if ((AllocationType & MEM_COMMIT) && (Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE))) { const ULONG nPages = PAGE_ROUND_UP(MemoryAreaLength) >> PAGE_SHIFT; //预留页面交换文件中的页面 MmReserveSwapPages(nPages); } *UBaseAddress = BaseAddress; //返回实际分配的地址 *URegionSize = MemoryAreaLength; //返回实际分配的长度 MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); return(STATUS_SUCCESS); }
2、页面异常
如通过传递MEM_COMMIT调用NtAllocateVirtualMemory后,如成功,系统会分配指定的虚存区间,并将其标志置为MEM_COMMIT,但并不实际映射到物理页面。当用户需要访问这块已分配区间时,则会发生页面异常(缺页异常)。在异常处理中,才为此虚存区间建立物理页面映射。
这样做的考虑在于,在某些情况下,用户要求分配空间,但不一定会立即使用,那么,这样的物理页面占用是没有意义的。所以,将物理页面映射操作延迟到访问时,进一步提高物理内存利用率。
页面发生异常时,内核会调用异常处理函数MmAccessFault(),这个函数由底层的异常响应程序调用。当发生某种异常时,CPU首先进入相应的异常响应程序,在那里再根据具体情况调用不同的处理程序。
MmAccessFault代码如下:
NTSTATUS NTAPI MmAccessFault(IN BOOLEAN StoreInstruction, IN PVOID Address, IN KPROCESSOR_MODE Mode, IN PVOID TrapInformation) { PMEMORY_AREA MemoryArea; ... ... //StoreInstruction为0,标示缺页;非0,标示越权 if (StoreInstruction) { /* Call access fault */ //因越权访问而引起 return MmpAccessFault(Mode, (ULONG_PTR)Address, TrapInformation ? FALSE : TRUE); } else { /* Call not present */ //因缺页而引起(未建立物理页面映射) return MmNotPresentFault(Mode, (ULONG_PTR)Address, TrapInformation ? FALSE : TRUE); } }
缺页异常处理代码:
NTSTATUS NTAPI MmNotPresentFault(KPROCESSOR_MODE Mode, ULONG_PTR Address, BOOLEAN FromMdl) { PMMSUPPORT AddressSpace; MEMORY_AREA* MemoryArea; NTSTATUS Status; BOOLEAN Locked = FromMdl; extern PMMPTE MmSharedUserDataPte; if (KeGetCurrentIrql() >= DISPATCH_LEVEL) { return(STATUS_UNSUCCESSFUL); } if (Address >= (ULONG_PTR)MmSystemRangeStart) { //异常发生于系统空间 if (Mode != KernelMode) { DPRINT1("Address: %x/n", Address); return(STATUS_ACCESS_VIOLATION); } AddressSpace = MmGetKernelAddressSpace(); } else //异常发生于用户空间 { //获取AVL树 AddressSpace = &PsGetCurrentProcess()->Vm; } if (!FromMdl) { MmLockAddressSpace(AddressSpace); } do { //在AVL树中搜寻异常区间 MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)Address); if (MemoryArea == NULL || MemoryArea->DeleteInProgress) { //区间尚未分配,或正在被删除 if (!FromMdl) { MmUnlockAddressSpace(AddressSpace); } return (STATUS_ACCESS_VIOLATION); } //搜索成功,根据区间类型处理 switch (MemoryArea->Type) { case MEMORY_AREA_PAGED_POOL: { Status = MmCommitPagedPoolAddress((PVOID)Address, Locked); break; } case MEMORY_AREA_SYSTEM: Status = STATUS_ACCESS_VIOLATION; break; case MEMORY_AREA_SECTION_VIEW: Status = MmNotPresentFaultSectionView(AddressSpace, MemoryArea, (PVOID)Address, Locked); break; //一般虚存区间 case MEMORY_AREA_VIRTUAL_MEMORY: case MEMORY_AREA_PEB_OR_TEB: Status = MmNotPresentFaultVirtualMemory(AddressSpace, MemoryArea, (PVOID)Address, Locked); break; case MEMORY_AREA_SHARED_DATA: *MiAddressToPte(USER_SHARED_DATA) = *MmSharedUserDataPte; Status = STATUS_SUCCESS; break; default: Status = STATUS_ACCESS_VIOLATION; break; } } while (Status == STATUS_MM_RESTART_OPERATION); if (!FromMdl) { MmUnlockAddressSpace(AddressSpace); } return(Status); }
对于一般虚存区间,使用MmNotPresentFaultVirtualMemory处理,此函数的处理过程大致为:
①使用MmRequestPageMemoryConsumer申请空闲的物理页面
②使用MmCreateVirtualMapping建立物理页面与虚存区块的映射