1、共享映射区
对于用户空间的映射,一个物理页面通常只被映射到一个进程用户空间。
对于系统空间的映射,是由所有进程共享的。
但是,一个物理页面也可以被映射到多个进程的用户空间,映射到的虚拟地址可以不尽相同。由这样的物理页面映射在多个进程虚存空间形成的连续区间,称为"共享映射区"。
2、共享映射区存在的原因
①系统使用共享映射区载入并运行.exe和DLL文件。这大量的节约了页交换文件空间及程序启动的时间
②用户可使用共享映射区来访问磁盘上的数据文件。使我们可以避免直接对文件进行IO操作和对文件内容进行缓存
③通过使用共享映射区,可以在一台机器的不同进程间共享数据,效率很高。
3、创建共享映射区
若要创建共享影射区,需执行如下三个步骤:
①创建共享映射区对象
②分配虚存空间
③建立映射
3.1、创建共享映射区对象
首先,创建共享映射区对象使用NtCreateSection
NtCreateSection (OUT PHANDLE SectionHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPTIONAL, IN ULONG SectionPageProtection OPTIONAL, IN ULONG AllocationAttributes, IN HANDLE FileHandle OPTIONAL)
最后一个参数FileHandle,如要创建文件映射区,则应该首先打开文件,并获取文件句柄填入;如填入NULL,则创建共享映射区。
其实现为:
NTSTATUS NTAPI NtCreateSection (OUT PHANDLE SectionHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPTIONAL, IN ULONG SectionPageProtection OPTIONAL, IN ULONG AllocationAttributes, IN HANDLE FileHandle OPTIONAL) { LARGE_INTEGER SafeMaximumSize; PVOID SectionObject; KPROCESSOR_MODE PreviousMode; NTSTATUS Status; ... Status = MmCreateSection(&SectionObject, DesiredAccess, ObjectAttributes, MaximumSize, SectionPageProtection, AllocationAttributes, FileHandle, NULL); ... return Status; }
显然,其函数主体为MmCreateSection
其实现为:
NTSTATUS NTAPI MmCreateSection (OUT PVOID * Section, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize, IN ULONG SectionPageProtection, IN ULONG AllocationAttributes, IN HANDLE FileHandle OPTIONAL, IN PFILE_OBJECT File OPTIONAL) { ULONG Protection; PROS_SECTION_OBJECT *SectionObject = (PROS_SECTION_OBJECT *)Section; /* * Check the protection */ Protection = SectionPageProtection & ~(PAGE_GUARD|PAGE_NOCACHE); if (Protection != PAGE_READONLY && Protection != PAGE_READWRITE && Protection != PAGE_WRITECOPY && Protection != PAGE_EXECUTE && Protection != PAGE_EXECUTE_READ && Protection != PAGE_EXECUTE_READWRITE && Protection != PAGE_EXECUTE_WRITECOPY) { return STATUS_INVALID_PAGE_PROTECTION; } //可执行文件 if (AllocationAttributes & SEC_IMAGE) { return(MmCreateImageSection(SectionObject, DesiredAccess, ObjectAttributes, MaximumSize, SectionPageProtection, AllocationAttributes, FileHandle)); } //普通数据文件 if (FileHandle != NULL) { return(MmCreateDataFileSection(SectionObject, DesiredAccess, ObjectAttributes, MaximumSize, SectionPageProtection, AllocationAttributes, FileHandle)); } //共享映射区(没有文件句柄) return(MmCreatePageFileSection(SectionObject, DesiredAccess, ObjectAttributes, MaximumSize, SectionPageProtection, AllocationAttributes)); }
这个函数一开始进行页面保护模式的合理性检查,检查后,就根据目标文件的性质不同进行不同的处理:
①如果是一个可执行文件,就通过MmCreateImageSection处理。因可执行文件有着特殊的结构。所以当做特例处理。
②如果是普通数据文件,由MmCreateDataFileSection创建文件映射区
③如果没有给定目标文件,那么就是创建“共享内存区”。(其实是以页面交换文件为目标文件)
以MmCreateDataFileSection为例,分析一下创建文件映射区的过程。
此函数中做了如下几件事情:
①创建映射区对象,由ObCreateObject完成
②获取数据文件对象,由ObReferenceObjectByHandle完成,并获取到数据文件的FILE_OBJECT结构指针
③构造一个映射段MM_SECTION_SEGMENT数据结构(ExAllocatePoolWithTag完成),让映射区对象与数据文件对象中的相关指针指向这个映射段。
④使映射区对象中的FileObject字段指向数据文件对象。
关键代码如下:
NTSTATUS NTAPI MmCreateDataFileSection(PROS_SECTION_OBJECT *SectionObject, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PLARGE_INTEGER UMaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle) { PROS_SECTION_OBJECT Section; NTSTATUS Status; LARGE_INTEGER MaximumSize; PFILE_OBJECT FileObject; PMM_SECTION_SEGMENT Segment; ULONG FileAccess; IO_STATUS_BLOCK Iosb; LARGE_INTEGER Offset; CHAR Buffer; FILE_STANDARD_INFORMATION FileInfo; /* * 创建映射区对象Section */ Status = ObCreateObject(ExGetPreviousMode(), MmSectionObjectType, ObjectAttributes, ExGetPreviousMode(), NULL, sizeof(ROS_SECTION_OBJECT), 0, 0, (PVOID*)(PVOID)&Section); /* * 初始化映射区对象Section */ RtlZeroMemory(Section, sizeof(ROS_SECTION_OBJECT)); Section->SectionPageProtection = SectionPageProtection; Section->AllocationAttributes = AllocationAttributes; .... /* * 获取数据文件对象FileObject */ Status = ObReferenceObjectByHandle(FileHandle, FileAccess, IoFileObjectType, ExGetPreviousMode(), (PVOID*)(PVOID)&FileObject, NULL); ... /* 如果数据文件此前尚未用作文件映射区的后盾 * 则构造映射段MM_SECTION_SEGMENT结构 */ if (FileObject->SectionObjectPointer->DataSectionObject == NULL) { Segment = ExAllocatePoolWithTag(NonPagedPool, sizeof(MM_SECTION_SEGMENT), TAG_MM_SECTION_SEGMENT); //使映射区对象中的相关指针指向映射段 Section->Segment = Segment; Segment->ReferenceCount = 1; ExInitializeFastMutex(&Segment->Lock); /* * Set the lock before assigning the segment to the file object */ ExAcquireFastMutex(&Segment->Lock); //使数据文件对象中的相关指针指向映射段 FileObject->SectionObjectPointer->DataSectionObject = (PVOID)Segment; //映射段初始化 Segment->FileOffset = 0; Segment->Protection = SectionPageProtection; Segment->Flags = MM_DATAFILE_SEGMENT; Segment->Characteristics = 0; Segment->WriteCopy = FALSE; if (AllocationAttributes & SEC_RESERVE) { Segment->Length = Segment->RawLength = 0; } else { Segment->RawLength = MaximumSize.u.LowPart; Segment->Length = PAGE_ROUND_UP(Segment->RawLength); } Segment->VirtualAddress = 0; RtlZeroMemory(&Segment->PageDirectory, sizeof(SECTION_PAGE_DIRECTORY)); } else { //如果数据文件已经映射到某个映射段,则可能需要扩展映射段大小(如果需要) Segment = (PMM_SECTION_SEGMENT)FileObject->SectionObjectPointer-> DataSectionObject; Section->Segment = Segment; (void)InterlockedIncrementUL(&Segment->ReferenceCount); MmLockSectionSegment(Segment); if (MaximumSize.u.LowPart > Segment->RawLength && !(AllocationAttributes & SEC_RESERVE)) { Segment->RawLength = MaximumSize.u.LowPart; Segment->Length = PAGE_ROUND_UP(Segment->RawLength); } } MmUnlockSectionSegment(Segment); //使映射区对象中的FileObject字段指向数据文件对象 Section->FileObject = FileObject; Section->MaximumSize = MaximumSize; CcRosReferenceCache(FileObject); *SectionObject = Section; return(STATUS_SUCCESS); } 、
3.2 映射区与映射段的区别
一个映射区由一个或多个映射段组成。对于映射的是可执行文件,其文件本身就分为多个段,例如代码段、数据段等,所以映射区也得要分成若干个映射段;对于映射的是数据文件,则只有一个映射段。
映射区对象结构
typedef struct _ROS_SECTION_OBJECT { CSHORT Type; CSHORT Size; LARGE_INTEGER MaximumSize; ULONG SectionPageProtection; ULONG AllocationAttributes; PFILE_OBJECT FileObject; //指向文件对象 union { PMM_IMAGE_SECTION_OBJECT ImageSection; //可执行文件 PMM_SECTION_SEGMENT Segment; //数据文件(单个映射段) }; } ROS_SECTION_OBJECT, *PROS_SECTION_OBJECT;
映射段对象结构
typedef struct _MM_SECTION_SEGMENT { LONG FileOffset; /* 本映射段起点对应于文件内部的位移*/ ULONG_PTR VirtualAddress; /* Start offset into the address range for image sections */ ULONG RawLength; /* length of the segment which is part of the mapped file */ ULONG Length; /* absolute length of the segment */ ULONG Protection; FAST_MUTEX Lock; /* lock which protects the page directory */ ULONG ReferenceCount; SECTION_PAGE_DIRECTORY PageDirectory; //页面目录(指向一个映射段页面表) ULONG Flags; ULONG Characteristics; BOOLEAN WriteCopy; } MM_SECTION_SEGMENT, *PMM_SECTION_SEGMENT;
3.3、分配虚存空间并建立映射
这时,映射区对象建立完毕,而并未实际建立映射。实际的映射是通过系统调用NtMapViewOfSection完成的。
这个系统调用的作用是,将一个映射区对象的一部分或全部映射到某个进程的用户空间。
对于数据文件,其最终调用的是MmMapViewOfSegment(因数据文件只有一个段)。
static NTSTATUS MmMapViewOfSegment(PMMSUPPORT AddressSpace, PROS_SECTION_OBJECT Section, PMM_SECTION_SEGMENT Segment, PVOID* BaseAddress, SIZE_T ViewSize, ULONG Protect, ULONG ViewOffset, ULONG AllocationType) { PMEMORY_AREA MArea; NTSTATUS Status; PHYSICAL_ADDRESS BoundaryAddressMultiple; BoundaryAddressMultiple.QuadPart = 0; Status = MmCreateMemoryArea(AddressSpace, MEMORY_AREA_SECTION_VIEW, BaseAddress, ViewSize, Protect, &MArea, FALSE, AllocationType, BoundaryAddressMultiple); if (!NT_SUCCESS(Status)) { return(Status); } ObReferenceObject((PVOID)Section); MArea->Data.SectionData.Segment = Segment; MArea->Data.SectionData.Section = Section; MArea->Data.SectionData.ViewOffset = ViewOffset; MArea->Data.SectionData.WriteCopyView = FALSE; MmInitializeRegion(&MArea->Data.SectionData.RegionListHead, ViewSize, 0, Protect); return(STATUS_SUCCESS); }
此函数很简单,就是申请一块虚存空间,并将对应的MEMORY_AREA结构的分量设置成指向映射区就完事了。