系列文章目录
文章目录
- 系列文章目录
- 3.4 共享映射区(Section)
- NtCreateSection ()
- MmCreateSection ()
- MmCreateDataFileSection()
- NtMapViewOfSection()
- MmNotPresentFaultSectionView()
3.4 共享映射区(Section)
对于用户空间的映射,一个物理页面通常只属于一个进程,即只被映射到一个进程的用户空间,因而其页面号只出现在一个进程的页面映射表中。注意这里说的是用户空间,至于系统空间的页面则本来就是由所有进程所共享的。但是,一个物理页面也可以被映射到多个进程的用户空间,可以出现在相同的地址上,也可以出现在不同的地址上。由这样的物理页面映射在虚存空间形成的连续区间,就称为“共享映射区(Section)”。之所以要有共享映射区,是因为:
两个或更多进程可以通过共享的页面共享信息,并可借以交换信息,作为一种进程间通信的手段。
共享的页面可以被映射到一个磁盘文件,以该文件为倒换文件,利用页面的倒换机制实现文件的读写。这样,就可以把整个文件或者文件的一部分映射到内存,用户空间的程序可以像访问内存一样方便地读写文件,而不再使用“读文件”、“写文件”等系统调用由于一个文件可以同时被多个进程所打开和操作,因而这种文件映射就需要能被多个进程所共享。
在实际应用中,后者所带来的方便从而其重要性远远超过了前者,以至于与其说“共享映射区”倒不如说“文件映射区”更为贴切了。在实践中,哪怕是单个进程独享的文件操作也倾向于采用文件映射区,实际上现在 Windows应用软件中对于磁盘文件已经不太使用“读文件”、“写文件”等系统调用了。
要创建一个文件映射区,需要先打开目标文件,获取打开该文件的“句柄”(见对象管理和文件系统部分),再以该句柄为参数(之一)调用系统调用NtCreateSection()。但是也可以不要目标文件,即以NULL为文件句柄,而把映射区用做进程间的共享内存区。其实,所谓有没有目标文件只是概念上的,只要是可倒换的页面就总要有个去处,区别只不过在于用户是否可见而已。
NtCreateSection ()
/*
* @implemented
*/
NTSTATUS STDCALL
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)
{
....
PreviousMode = ExGetPreviousMode();
if(MaximumSize != NULL && PreviousMode != KernelMode)
{
_SEH_TRY
{
/* make a copy on the stack *///将参数复制到系统空间
SafeMaximumSize = ProbeForReadLargeInteger(MaximumSize);
MaximumSize = &SafeMaximumSize;
}
....
Status = MmCreateSection(&SectionObject,
DesiredAccess,
ObjectAttributes,
MaximumSize,
SectionPageProtection,
AllocationAttributes,
FileHandle,
NULL);
if (NT_SUCCESS(Status))
{//将创建的 Section 对象插入对象目录和本进程的句柄表
Status = ObInsertObject ((PVOID)SectionObject,
NULL,
DesiredAccess,
0,
NULL,
SectionHandle);
}
return Status;
}
显然,这个函数的主体是MmCreateSectionO),我们接着往下看。
MmCreateSection ()
[NtCreateSection()> MmCreateSection()]
NTSTATUS STDCALL
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_NOACCESS &&
Protection != PAGE_READONLY &&
Protection != PAGE_READWRITE &&
Protection != PAGE_WRITECOPY &&
Protection != PAGE_EXECUTE &&
Protection != PAGE_EXECUTE_READ &&
Protection != PAGE_EXECUTE_READWRITE &&
Protection != PAGE_EXECUTE_WRITECOPY)
{
CHECKPOINT1;
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));
}
首先是对于参数 SectionPageProtection 即页面保护模式的合理性检查。通过了检查之后,就因目标文件性质的不同而调用不同的函数:
如果参数 AllocationAttributes 表明这是个可执行映像文件,就通过 MmCreatelmageSection()
创建其映射区。可执行文件有着特殊的结构,所以在映射时要加以特殊的处理。另一方面可执行映像文件中可以有很多不同的段,对于这些不同的段可能会有不同的映射。所以,可执行映像文件是作为一种特例对待的。
只要不是可执行映像,而又存在目标文件,就属于普通的数据文件,数据文件的映射区是由MmCreateDataFileSection(创建的。
如果没有给定目标文件,就说明旨在创建一个可以为多个进程共享的“共享内存区"。顺便提一下,从函数名 MmCreatePageFileSection()可以看出,所谓没有目标文件其实是以PageFile即倒换页面文件为目标文件的。
在这里我们只以 MmCreateDataFileSection()为例考察普通(数据)文件的文件映射区,其余两种情景留给读者自己研究。下面分段阅读MmCreateDataFileSection()的代码。
MmCreateDataFileSection()
[NtCreateSection()>MmCreateSection()> MmCreateDataFileSection()]
NTSTATUS
NTAPI
MmCreateDataFileSection(PROS_SECTION_OBJECT *SectionObject,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER UMaximumSize,
ULONG SectionPageProtection,
ULONG AllocationAttributes,
HANDLE FileHandle)
/*
* Create a section backed by a data file //创建 Section 对象
*/
{
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;
/*
* Create the section
*/
Status = ObCreateObject(ExGetPreviousMode(),
MmSectionObjectType,
ObjectAttributes,
ExGetPreviousMode(),
NULL,
sizeof(ROS_SECTION_OBJECT),
0,
0,
(PVOID*)(PVOID)&Section);
if (!NT_SUCCESS(Status))
{
return(Status);
}
/*
* Initialize it
*/
Section->SectionPageProtection = SectionPageProtection;
Section->AllocationAttributes = AllocationAttributes;
Section->Segment = NULL;
/*
* Check file access required
*/
if (SectionPageProtection & PAGE_READWRITE ||
SectionPageProtection & PAGE_EXECUTE_READWRITE)
{
FileAccess = FILE_READ_DATA | FILE_WRITE_DATA;
}
else
{
FileAccess = FILE_READ_DATA;
}
/*
* Reference the file handle
*/
Status = ObReferenceObjectByHandle(FileHandle,
FileAccess,
IoFileObjectType,
UserMode,
(PVOID*)(PVOID)&FileObject,
NULL);
if (!NT_SUCCESS(Status))
{
ObDereferenceObject(Section);
return(Status);
}
/*
* FIXME: This is propably not entirely correct. We can't look into
* the standard FCB header because it might not be initialized yet
* (as in case of the EXT2FS driver by Manoj Paul Joseph where the
* standard file information is filled on first request).
*/
Status = IoQueryFileInformation(FileObject,
FileStandardInformation,
sizeof(FILE_STANDARD_INFORMATION),
&FileInfo,
&Iosb.Information);
if (!NT_SUCCESS(Status))
{
ObDereferenceObject(Section);
ObDereferenceObject(FileObject);
return Status;
}
/*
* FIXME: Revise this once a locking order for file size changes is
* decided
*/
if (UMaximumSize != NULL)
{
MaximumSize = *UMaximumSize;
}
else
{
MaximumSize = FileInfo.EndOfFile;
/* Mapping zero-sized files isn't allowed. */
if (MaximumSize.QuadPart == 0)
{
ObDereferenceObject(Section);
ObDereferenceObject(FileObject);
return STATUS_FILE_INVALID;
}
}
if (MaximumSize.QuadPart > FileInfo.EndOfFile.QuadPart)
{
Status = IoSetInformation(FileObject,
FileAllocationInformation,
sizeof(LARGE_INTEGER),
&MaximumSize);
if (!NT_SUCCESS(Status))
{
ObDereferenceObject(Section);
ObDereferenceObject(FileObject);
return(STATUS_SECTION_NOT_EXTENDED);
}
}
if (FileObject->SectionObjectPointer == NULL ||
FileObject->SectionObjectPointer->SharedCacheMap == NULL)
{
/*
* Read a bit so caching is initiated for the file object.
* This is only needed because MiReadPage currently cannot
* handle non-cached streams.
*/
Offset.QuadPart = 0;
Status = ZwReadFile(FileHandle,
NULL,
NULL,
NULL,
&Iosb,
&Buffer,
sizeof (Buffer),
&Offset,
0);
if (!NT_SUCCESS(Status) && (Status != STATUS_END_OF_FILE))
{
ObDereferenceObject(Section);
ObDereferenceObject(FileObject);
return(Status);
}
if (FileObject->SectionObjectPointer == NULL ||
FileObject->SectionObjectPointer->SharedCacheMap == NULL)
{
/* FIXME: handle this situation */
ObDereferenceObject(Section);
ObDereferenceObject(FileObject);
return STATUS_INVALID_PARAMETER;
}
}
}
先通过 ObCreateObject0创建一个映射区对象,返回其数据结构指针Section,并按调用参数加以初始化,这是事情的一头。然后通过ObReferenceObjectByHandle()获取目标文件对象的FILE_OBJECT 数据结构 FileObject,并通过 IoQueryFileInformation()获取目标文件的基本信息,这是事情的另一头。要获取目标文件的什么基本信息呢?从代码中可以看出是文件的实际大小。系统调用NtCreateSection()只是创建映射区对象,而并没有实际将其映射到当前进程的用户空间,实际的映射要靠另一个系统调用 NtMapViewOfSection()完成。但是,NIMapViewOfSection()可以只实际映射其中的一部分,也可以是全部。实际的大小受限于调用NtCreateSection()时的参数UMaximumSize,即映射区大小的上限。如果这个参数是0就表示采用实际的文件大小,因而需要获取目标文件的实际大小。也许更为重要的是:如果给定的上限大于目标文件的实际大小,还要通过loSetlnformation()改变目标文件的大小,就是对目标文件加以扩充。扩充什么内容呢?一开始当然是空白,即在文件后面添上全0,到实际映射了之后就可以像对普通内存页面一样地读1写了(如果这个区间的页面保护模式允许的话)。
对于下面的系统调用ZwReadFile(),代码的作者加了注释,说这是因为目前ReactOs的缺页异常处理函数 MiReadPageO)还不够完善,它要求目标文件必须已经在内存中建立了缓冲存储的机制。而通过系统调用ZwReadFile()对目标文件进行一次哪怕只是一个字符的读出(代码中的 Buffer 只是一个字符),系统都会为该目标文件建立起缓冲存储的机制。
我们继续看 MmCreateDataFileSection()的代码:
[NtCreateSection()>MmCreateSection() > MmCreateDataFileSecton()]
/*
* Lock the file
*/
Status = MmspWaitForFileLock(FileObject);
if (Status != STATUS_SUCCESS)
{
ObDereferenceObject(Section);
ObDereferenceObject(FileObject);
return(Status);
}
/*
* If this file hasn't been mapped as a data file before then allocate a
* section segment to describe the data file mapping
*/
if (FileObject->SectionObjectPointer->DataSectionObject == NULL)
{
Segment = ExAllocatePoolWithTag(NonPagedPool, sizeof(MM_SECTION_SEGMENT),
TAG_MM_SECTION_SEGMENT);
if (Segment == NULL)
{
//KeSetEvent((PVOID)&FileObject->Lock, IO_NO_INCREMENT, FALSE);
ObDereferenceObject(Section);
ObDereferenceObject(FileObject);
return(STATUS_NO_MEMORY);
}
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
{
/*
* If the file is already mapped as a data file then we may need
* to extend it
*/
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);
Section->FileObject = FileObject;
Section->MaximumSize = MaximumSize;
CcRosReferenceCache(FileObject);
//KeSetEvent((PVOID)&FileObject->Lock, IO_NO_INCREMENT, FALSE);
*SectionObject = Section;
return(STATUS_SUCCESS);
然后,如果目标文件对象此前尚未用做文件映射区的后盾,就由xAlocatePoolWithTag()为其分配一个映射段数据结构,即MM_SECTIONSEGMENT数据结构,并加以初始化,再使映射区对象 Section 和目标文件对象 FileObject 中的相关指针都指向这个数据结构。
映射段(Segment)与映射区(Section)是什么关系呢?前面讲过,映射区所映射的可以是整个文件,并且有可能使用可执行文件。但是可执行文件是由多个“段”构成的,例如代码段、数据段等,所以映射区也得要分成若干个映射段才行。不过,MmCreateDataFileSection()所处理的是数据文件的映射,而数据文件只含有一个段,所以这里的代码中只涉及一个映射段。
在 ReactOs 内核中,映射区的数据结构是 ROS_SECTION_OBJECT,显然这与 Windows中的“正定义有所不同,但是这并不妨碍我们对映射区的理解:
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;
映射区的数据结构中必须有指向目标文件对象的指针,这是不言而喻的。如果目标文件是可执行映像文件,那就要有个指针指向另一个数据结构,即MM_IMAGE_SECTION_OBJECT,在那里有个映射段结构指针的数组,还有个字段说明段的数量,因为可执行映像文件里面段的数量是不固定的。而如果目标文件是数据文件,则只有一个段,所以直接使这个指针指向一个映射段数据结构即 MM_SECTION_SEGMENT 就可以了。
再看映射段的数据结构MM_SECTION_SEGMENT :
typedef struct _MM_SECTION_SEGMENT
{
LONGLONG FileOffset; /* start offset into the file for image sections */
ULONG_PTR VirtualAddress; /* dtart 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;
字段FileOffset 说明本映射段的起点对应于文件内部的位移,对于数据文件显然应该是0。读者可以结合这里的注释与上面的代码搞清其余字段的意义和作用。这里要说明的是作为内部成分的映射段页面目录,即SECTION_PAGE_DIRECTORY数据结构PageDirectory。顾名思义,这是个页面目录,其数据结构为:
typedef struct
{
PSECTION_PAGE_TABLE PageTables[NR_SECTION_PAGE_TABLES];
} SECTION_PAGE_DIRECTORY, *PSECTION_PAGE_DIRECTORY;
这是个指针数组,数组的大小NR_SECTION_PAGE_TABLES定义为1024,所以正好占一个页面。
每个指针指向一个映射段二级页面表,即SECTION_PAGE_TABLE 数据结构。
typedef struct
{
ULONG Entry[NR_SECTION_PAGE_ENTRIES];
} SECTION_PAGE_TABLE, *PSECTION_PAGE_TABLE;
这又是一个数组,数组的大小NR_SECTION_PAGE_ENTRIES也定义为1024,也占一个页面显然,映射段页面目录和映射段二级页面表构成了映射段的页面映射表,在形式上与内存的页面映射表一致。不过,只要目标文件不是太大,映射段页面目录中的大部分目录项都是NULL,因而二级映射表实际上不会占那么多的页面。
完成了这些操作之后,最后通过 CcRosReferenceCache()递增目标文件的BCB即“缓冲区控制块”的引用计数。
上面只是创建了(文件)映射区对象,而并未实际建立映射。实际的映射是通过另一个系统调用NMapViewOfSection()完成的,这个系统调用将一个映射区对象的一部分或全部映射到某个进程的用户空间,这个进程不必是当前进程。
NtMapViewOfSection()
NTSTATUS STDCALL
NtMapViewOfSection(IN HANDLE SectionHandle,
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress OPTIONAL,
IN ULONG ZeroBits OPTIONAL,
IN ULONG CommitSize,
IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
IN OUT PSIZE_T ViewSize,
IN SECTION_INHERIT InheritDisposition,
IN ULONG AllocationType OPTIONAL,
IN ULONG Protect)
{
....
//参数合理性检查
....
PreviousMode = ExGetPreviousMode();
if(PreviousMode != KernelMode)
{
...
//参数合理性检查
...
}
else
{
SafeBaseAddress = (BaseAddress != NULL ? *BaseAddress : NULL);
SafeSectionOffset.QuadPart = (SectionOffset != NULL ? SectionOffset->QuadPart : 0);
SafeViewSize = (ViewSize != NULL ? *ViewSize : 0);
}
SafeSectionOffset.LowPart = PAGE_ROUND_DOWN(SafeSectionOffset.LowPart);
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID*)(PVOID)&Process,
NULL);
if (!NT_SUCCESS(Status))
{
return(Status);
}
AddressSpace = (PMADDRESS_SPACE)&Process->VadRoot;
Status = ObReferenceObjectByHandle(SectionHandle,
SECTION_MAP_READ,
MmSectionObjectType,
PreviousMode,
(PVOID*)(PVOID)&Section,
NULL);
if (!(NT_SUCCESS(Status)))
{
DPRINT("ObReference failed rc=%x\n",Status);
ObDereferenceObject(Process);
return(Status);
}
Status = MmMapViewOfSection(Section,
(PEPROCESS)Process,
(BaseAddress != NULL ? &SafeBaseAddress : NULL),
ZeroBits,
CommitSize,
(SectionOffset != NULL ? &SafeSectionOffset : NULL),
(ViewSize != NULL ? &SafeViewSize : NULL),
InheritDisposition,
AllocationType,
Protect);
/* Check if this is an image for the current process */
if ((Section->AllocationAttributes & SEC_IMAGE) &&
(Process == PsGetCurrentProcess()) &&
(Status != STATUS_IMAGE_NOT_AT_BASE))
{
/* Notify the debugger */
DbgkMapViewOfSection(Section,
SafeBaseAddress,
SafeSectionOffset.LowPart,
SafeViewSize);
}
ObDereferenceObject(Section);
ObDereferenceObject(Process);
if(NT_SUCCESS(Status))
{
/* copy parameters back to the caller */
_SEH_TRY
{
if(BaseAddress != NULL)
{
*BaseAddress = SafeBaseAddress;
}
if(SectionOffset != NULL)
{
*SectionOffset = SafeSectionOffset;
}
if(ViewSize != NULL)
{
*ViewSize = SafeViewSize;
}
}
_SEH_HANDLE
{
Status = _SEH_GetExceptionCode();
}
_SEH_END;
}
return(Status);
}
先看参数。SectionHandle当然是映射区对象的句柄,而ProcessHandle是目标进程的句柄。像别的许多操作一样,NtMapViewOfSection()也不一定是针对当前进程的,只要能打开一个目标进程,就可以将一个映射区的部分或全部映射到它的用户空间。注意所映射的是“ViewOfSection”,这是一个Section 中的一个 View,所以一般而言是映射区的一部分,不过当然也可以是映射区的全部。BaseAddress、ZeroBit和CommitSize用于映射后的虚存区间,前者用来指定和返回映射的虚存地址。如果未指定具体的地址,即*BaeAddress为0,则ZeroBit指明了一个范围,表明实际分配的地址在其高位应该有多少个连续的0,即应该分配在什么部位。SectionOfset是指实际映射的内容在目标映射区对象从而目标文件中的位移,而ViewSize 则为实际映射的区间大小。
显然,这个系统调用的主体是MmMapViewOfSection()。
[NtMapViewOrSection() > MmMapViewOfSection()]
NTSTATUS STDCALL
MmMapViewOfSection(IN PVOID SectionObject,
IN PEPROCESS Process,
IN OUT PVOID *BaseAddress,
IN ULONG ZeroBits,
IN ULONG CommitSize,
IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
IN OUT PSIZE_T ViewSize,
IN SECTION_INHERIT InheritDisposition,
IN ULONG AllocationType,
IN ULONG Protect)
{
...
///检测参数protect的合理性
Section = (PROS_SECTION_OBJECT)SectionObject;
AddressSpace = (PMADDRESS_SPACE)&(Process)->VadRoot;
AllocationType |= (Section->AllocationAttributes & SEC_NO_CHANGE);
MmLockAddressSpace(AddressSpace);
if (Section->AllocationAttributes & SEC_IMAGE)
{
ULONG i;
ULONG NrSegments;
ULONG_PTR ImageBase;
ULONG ImageSize;
PMM_IMAGE_SECTION_OBJECT ImageSectionObject;
PMM_SECTION_SEGMENT SectionSegments;
ImageSectionObject = Section->ImageSection;
SectionSegments = ImageSectionObject->Segments;
NrSegments = ImageSectionObject->NrSegments;
ImageBase = (ULONG_PTR)*BaseAddress;
if (ImageBase == 0)
{
ImageBase = ImageSectionObject->ImageBase;
}
ImageSize = 0;
for (i = 0; i < NrSegments; i++)
{
if (!(SectionSegments[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
{
ULONG_PTR MaxExtent;
MaxExtent = (ULONG_PTR)SectionSegments[i].VirtualAddress +
SectionSegments[i].Length;
ImageSize = max(ImageSize, MaxExtent);
}
}
ImageSectionObject->ImageSize = ImageSize;
/* Check there is enough space to map the section at that point. */
if (MmLocateMemoryAreaByRegion(AddressSpace, (PVOID)ImageBase,
PAGE_ROUND_UP(ImageSize)) != NULL)
{
/* Fail if the user requested a fixed base address. */
if ((*BaseAddress) != NULL)
{
MmUnlockAddressSpace(AddressSpace);
return(STATUS_UNSUCCESSFUL);
}
/* Otherwise find a gap to map the image. */
ImageBase = (ULONG_PTR)MmFindGap(AddressSpace, PAGE_ROUND_UP(ImageSize), PAGE_SIZE, FALSE);
if (ImageBase == 0)
{
MmUnlockAddressSpace(AddressSpace);
return(STATUS_UNSUCCESSFUL);
}
}
for (i = 0; i < NrSegments; i++)
{
if (!(SectionSegments[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
{
PVOID SBaseAddress = (PVOID)
((char*)ImageBase + (ULONG_PTR)SectionSegments[i].VirtualAddress);
MmLockSectionSegment(&SectionSegments[i]);
//数据文件只有一个映射段,建立其映射
Status = MmMapViewOfSegment(AddressSpace,
Section,
&SectionSegments[i],
&SBaseAddress,
SectionSegments[i].Length,
SectionSegments[i].Protection,
0,
0);
MmUnlockSectionSegment(&SectionSegments[i]);
if (!NT_SUCCESS(Status))
{
MmUnlockAddressSpace(AddressSpace);
return(Status);
}
}
}
*BaseAddress = (PVOID)ImageBase;
}
else
{
/* check for write access */
if ((Protect & (PAGE_READWRITE|PAGE_EXECUTE_READWRITE)) &&
!(Section->SectionPageProtection & (PAGE_READWRITE|PAGE_EXECUTE_READWRITE)))
{
CHECKPOINT1;
return STATUS_SECTION_PROTECTION;
}
/* check for read access */
if ((Protect & (PAGE_READONLY|PAGE_WRITECOPY|PAGE_EXECUTE_READ|PAGE_EXECUTE_WRITECOPY)) &&
!(Section->SectionPageProtection & (PAGE_READONLY|PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY)))
{
CHECKPOINT1;
return STATUS_SECTION_PROTECTION;
}
/* check for execute access */
if ((Protect & (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY)) &&
!(Section->SectionPageProtection & (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY)))
{
CHECKPOINT1;
return STATUS_SECTION_PROTECTION;
}
if (ViewSize == NULL)
{
/* Following this pointer would lead to us to the dark side */
/* What to do? Bugcheck? Return status? Do the mambo? */
KEBUGCHECK(MEMORY_MANAGEMENT);
}
if (SectionOffset == NULL)
{
ViewOffset = 0;
}
else
{
ViewOffset = SectionOffset->u.LowPart;
}
if ((ViewOffset % PAGE_SIZE) != 0)
{
MmUnlockAddressSpace(AddressSpace);
return(STATUS_MAPPED_ALIGNMENT);
}
if ((*ViewSize) == 0)
{
(*ViewSize) = Section->MaximumSize.u.LowPart - ViewOffset;
}
else if (((*ViewSize)+ViewOffset) > Section->MaximumSize.u.LowPart)
{
(*ViewSize) = Section->MaximumSize.u.LowPart - ViewOffset;
}
MmLockSectionSegment(Section->Segment);
Status = MmMapViewOfSegment(AddressSpace,
Section,
Section->Segment,
BaseAddress,
*ViewSize,
Protect,
ViewOffset,
AllocationType & (MEM_TOP_DOWN|SEC_NO_CHANGE));
MmUnlockSectionSegment(Section->Segment);
if (!NT_SUCCESS(Status))
{
MmUnlockAddressSpace(AddressSpace);
return(Status);
}
}
MmUnlockAddressSpace(AddressSpace);
return(STATUS_SUCCESS);
}
这个函数也可用于可执行映像文件的多个映射段(Segment)的映射,而数据文件的映射则只涉及一个段的映射。在这里我们需要把注意力集中于数据文件的单段映射,所以跳过了可执行映像的映射。读者明白了单段的映射以后,自不难举一反三自己搞明白可执行映像的多段映射。一个映射段的映射由MmMapViewOfSegment()实现。
[NtMapViewOfSection( > MmMapViewOfSection() > MmMapViewOfSegment()]
NTSTATUS static
MmMapViewOfSegment(PMADDRESS_SPACE AddressSpace,
PROS_SECTION_OBJECT Section,
PMM_SECTION_SEGMENT Segment,
PVOID* BaseAddress,
SIZE_T ViewSize,
ULONG Protect,
ULONG ViewOffset,
ULONG AllocationType)
{
PMEMORY_AREA MArea;
...
BoundaryAddressMultiple.QuadPart = 0;
Status = MmCreateMemoryArea(AddressSpace,
MEMORY_AREA_SECTION_VIEW,
BaseAddress,
ViewSize,
Protect,
&MArea,
FALSE,
AllocationType,
BoundaryAddressMultiple);
if (!NT_SUCCESS(Status))
{
DPRINT1("Mapping between 0x%.8X and 0x%.8X failed (%X).\n",
(*BaseAddress), (char*)(*BaseAddress) + ViewSize, 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);
}
这个函数的代码也许远比读者事先想象的简单。它只是通过MmCreateMemoryArea()分配一个地址区间MArea,再把有关的参数如ViewOfset 设置进去,并正确设置其指针Segment和Section
就完事了。
MEMORY_AREA 数据结构中的 Data 是个 union,根据区间的类型不同而可以是 VirtualMemoryData或者 SectionData数据结构。对于映射区,这当然是个SectionData结构。这样,在完成了MArea的初始化以后,就可以从一个具体的虚拟地址出发找到其所属的MEMORY_AREA 数据结构,再找到相应的 Section 和Segment。
当然,所谓建立映射区在一个进程的空间中的映射,总得要将一些物理页面映射到这个进程的用户(虚存)地址空间。那么为什么我们在这里看不到这种页面映射的建立呢?原因很简单,页面映射的实际建立要到因为受到访问而发生缺页中断时才进行,并且是访问到几个页面就建立几个页面的映射,绝不做无用功。发生缺页中断的时候,根据受到访问的虚存地址和记录在这些数据结构中的信息可以推算出具体的页面及其镜像在目标文件中的位置,从而倒入其镜像页面,到那时候才需要分配物理页面并建立具体的页面映射。
读者也许会问,页面映射是由进程的页面映射表实现的,现在又有了个映射段页面表,这之间是什么关系,为什么需要有映射段页面表呢?当然,从MMU的角度看,页面映射仍旧是由进程的页面映射表实现的,因为写入控制寄存器CR3的是这个映射表的地址,MMU只认这个映射表。所以,当(虚存)页面在某个物理页面中时,页面映射表中的PTE如常指向这个物理页面。同时,映射段页面表中的相应表项也指向这个物理页面。但是进程的页面表是以虚存页面号为下标,而映射段页面表则实质上是以目标文件(更确切地说是文件内某个“视图(View)”)中的页面号为下标注意这个物理页面可以出现在多个进程的页面映射表中,在同一个进程的页面表中也可以出现多次。可是,当目标页面的内容不在物理内存中时,情况就有些不同了。
首先,如果共享映射区以页面倒换文件为后备而不以普通文件为后备,那就与一般的页面映射相同。所以,如果页面映射表中的相应PTE最低位为0,但是整个PTE非0,就说明页面的后备在倒换文件中,这就是普通的页面倒入问题。
如果共享映射区以普通文件为后备,那么32位的PTE已经不足以说明目标页面所在的文件和在目标文件内部的位置了,所以把PTE设成0,而通过映射段页面表来指明页面在文件中的位置。段映射表属于具体的文件映射区,而文件映射区又总是与具体的文件挂钩,所以此时不必说明在哪一个文件中了。
为帮助理解这一点,我们不妨再看看发生页面异常时的情景。我们在前面已经看到,发生页面异常时的总的入口是MmccessFault()。在那里,由于所发生的是缺页异常,就进入了MmNotPresentFault()。然后,根据发生异常的地址找到了相应的虚存区间即 MEMORY_AREA 数据结构,而这个区间的类型是 MEMORY_AREA_SECTION_VIEW,说明这个区间是用于映射区的,所以就通过 MmNotPresentFaultSectionView()处理页面异常。
MmNotPresentFaultSectionView()
[MmAccessFault() > MmNotPresentFault()> MmNotPresentFaultSectionView()]
NTSTATUS
NTAPI
MmNotPresentFaultSectionView(PMADDRESS_SPACE AddressSpace,
MEMORY_AREA* MemoryArea,
PVOID Address,
BOOLEAN Locked)
{
ULONG Offset;
PFN_TYPE Page;
NTSTATUS Status;
PVOID PAddress;
PROS_SECTION_OBJECT Section;
PMM_SECTION_SEGMENT Segment;
ULONG Entry;
ULONG Entry1;
ULONG Attributes;
PMM_PAGEOP PageOp;
PMM_REGION Region;
BOOLEAN HasSwapEntry;
/*
* There is a window between taking the page fault and locking the
* address space when another thread could load the page so we check
* that.
*/
if (MmIsPagePresent(AddressSpace->Process, Address))
{
if (Locked)
{
MmLockPage(MmGetPfnForProcess(AddressSpace->Process, Address));
}
return(STATUS_SUCCESS);
}
PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE);
Offset = (ULONG_PTR)PAddress - (ULONG_PTR)MemoryArea->StartingAddress
+ MemoryArea->Data.SectionData.ViewOffset;
Segment = MemoryArea->Data.SectionData.Segment;
Section = MemoryArea->Data.SectionData.Section;
Region = MmFindRegion(MemoryArea->StartingAddress,
&MemoryArea->Data.SectionData.RegionListHead,
Address, NULL);
/*
* Lock the segment
*/
MmLockSectionSegment(Segment);
/*
* Check if this page needs to be mapped COW
*/
if ((Segment->WriteCopy || MemoryArea->Data.SectionData.WriteCopyView) &&
(Region->Protect == PAGE_READWRITE ||
Region->Protect == PAGE_EXECUTE_READWRITE))
{
Attributes = Region->Protect == PAGE_READWRITE ? PAGE_READONLY : PAGE_EXECUTE_READ;
}
else
{
Attributes = Region->Protect;
}
/*
* Get or create a page operation descriptor
*/
PageOp = MmGetPageOp(MemoryArea, NULL, 0, Segment, Offset, MM_PAGEOP_PAGEIN, FALSE);
if (PageOp == NULL)
{
DPRINT1("MmGetPageOp failed\n");
KEBUGCHECK(0);
}
/*
* Check if someone else is already handling this fault, if so wait
* for them
*/
if (PageOp->Thread != PsGetCurrentThread())
{
MmUnlockSectionSegment(Segment);
MmUnlockAddressSpace(AddressSpace);
Status = MmspWaitForPageOpCompletionEvent(PageOp);
/*
* Check for various strange conditions
*/
if (Status != STATUS_SUCCESS)
{
DPRINT1("Failed to wait for page op, status = %x\n", Status);
KEBUGCHECK(0);
}
if (PageOp->Status == STATUS_PENDING)
{
DPRINT1("Woke for page op before completion\n");
KEBUGCHECK(0);
}
MmLockAddressSpace(AddressSpace);
/*
* If this wasn't a pagein then restart the operation
*/
if (PageOp->OpType != MM_PAGEOP_PAGEIN)
{
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_MM_RESTART_OPERATION);
}
/*
* If the thread handling this fault has failed then we don't retry
*/
if (!NT_SUCCESS(PageOp->Status))
{
Status = PageOp->Status;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(Status);
}
MmLockSectionSegment(Segment);
/*
* If the completed fault was for another address space then set the
* page in this one.
*/
if (!MmIsPagePresent(AddressSpace->Process, Address))
{
Entry = MmGetPageEntrySectionSegment(Segment, Offset);
HasSwapEntry = MmIsPageSwapEntry(AddressSpace->Process, (PVOID)PAddress);
if (PAGE_FROM_SSE(Entry) == 0 || HasSwapEntry)
{
/*
* The page was a private page in another or in our address space
*/
MmUnlockSectionSegment(Segment);
MmspCompleteAndReleasePageOp(PageOp);
return(STATUS_MM_RESTART_OPERATION);
}
Page = PFN_FROM_SSE(Entry);
MmSharePageEntrySectionSegment(Segment, Offset);
/* FIXME: Should we call MmCreateVirtualMappingUnsafe if
* (Section->AllocationAttributes & SEC_PHYSICALMEMORY) is true?
*/
Status = MmCreateVirtualMapping(AddressSpace->Process,
Address,
Attributes,
&Page,
1);
if (!NT_SUCCESS(Status))
{
DPRINT1("Unable to create virtual mapping\n");
KEBUGCHECK(0);
}
MmInsertRmap(Page, AddressSpace->Process, (PVOID)PAddress);
}
if (Locked)
{
MmLockPage(Page);
}
MmUnlockSectionSegment(Segment);
PageOp->Status = STATUS_SUCCESS;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_SUCCESS);
}
HasSwapEntry = MmIsPageSwapEntry(AddressSpace->Process, (PVOID)PAddress);
if (HasSwapEntry)
{
/*
* Must be private page we have swapped out.
*/
SWAPENTRY SwapEntry;
/*
* Sanity check
*/
if (Segment->Flags & MM_PAGEFILE_SEGMENT)
{
DPRINT1("Found a swaped out private page in a pagefile section.\n");
KEBUGCHECK(0);
}
MmUnlockSectionSegment(Segment);
MmDeletePageFileMapping(AddressSpace->Process, (PVOID)PAddress, &SwapEntry);
MmUnlockAddressSpace(AddressSpace);
Status = MmRequestPageMemoryConsumer(MC_USER, TRUE, &Page);
if (!NT_SUCCESS(Status))
{
KEBUGCHECK(0);
}
Status = MmReadFromSwapPage(SwapEntry, Page);
if (!NT_SUCCESS(Status))
{
DPRINT1("MmReadFromSwapPage failed, status = %x\n", Status);
KEBUGCHECK(0);
}
MmLockAddressSpace(AddressSpace);
Status = MmCreateVirtualMapping(AddressSpace->Process,
Address,
Region->Protect,
&Page,
1);
if (!NT_SUCCESS(Status))
{
DPRINT("MmCreateVirtualMapping failed, not out of memory\n");
KEBUGCHECK(0);
return(Status);
}
/*
* Store the swap entry for later use.
*/
MmSetSavedSwapEntryPage(Page, SwapEntry);
/*
* Add the page to the process's working set
*/
MmInsertRmap(Page, AddressSpace->Process, (PVOID)PAddress);
/*
* Finish the operation
*/
if (Locked)
{
MmLockPage(Page);
}
PageOp->Status = STATUS_SUCCESS;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_SUCCESS);
}
/*
* Satisfying a page fault on a map of /Device/PhysicalMemory is easy
*/
if (Section->AllocationAttributes & SEC_PHYSICALMEMORY)
{
MmUnlockSectionSegment(Segment);
/*
* Just map the desired physical page
*/
Page = Offset >> PAGE_SHIFT;
Status = MmCreateVirtualMappingUnsafe(AddressSpace->Process,
Address,
Region->Protect,
&Page,
1);
if (!NT_SUCCESS(Status))
{
DPRINT("MmCreateVirtualMappingUnsafe failed, not out of memory\n");
KEBUGCHECK(0);
return(Status);
}
/*
* Don't add an rmap entry since the page mapped could be for
* anything.
*/
if (Locked)
{
MmLockPageUnsafe(Page);
}
/*
* Cleanup and release locks
*/
PageOp->Status = STATUS_SUCCESS;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_SUCCESS);
}
/*
* Map anonymous memory for BSS sections
*/
if (Segment->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA)
{
MmUnlockSectionSegment(Segment);
Status = MmRequestPageMemoryConsumer(MC_USER, FALSE, &Page);
if (!NT_SUCCESS(Status))
{
MmUnlockAddressSpace(AddressSpace);
Status = MmRequestPageMemoryConsumer(MC_USER, TRUE, &Page);
MmLockAddressSpace(AddressSpace);
}
if (!NT_SUCCESS(Status))
{
KEBUGCHECK(0);
}
Status = MmCreateVirtualMapping(AddressSpace->Process,
Address,
Region->Protect,
&Page,
1);
if (!NT_SUCCESS(Status))
{
DPRINT("MmCreateVirtualMapping failed, not out of memory\n");
KEBUGCHECK(0);
return(Status);
}
MmInsertRmap(Page, AddressSpace->Process, (PVOID)PAddress);
if (Locked)
{
MmLockPage(Page);
}
/*
* Cleanup and release locks
*/
PageOp->Status = STATUS_SUCCESS;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_SUCCESS);
}
}
先创建或获取一个MM_PAGEOP,如果别的线程已经在处理这个地址区块的映射就等待,这跟前面看见过的一样。如果没有别的线程在处理,那么当前线程就要担当起这个任务了。为此,先检查一下目标进程页面映射表中的相应PTE,如果PTE中的PA_PRESENT位为0,但整个表项非 0,这便是以倒换文件为后备的共享映射区,所以读入倒换页面并建立物理内存页面的映射。
但是,如果相应的PTE是0,那就不同了,就要进一步看映射段页面表中的相应表项。为此,要根据发生异常时的虚拟地址计算出这个地址在所在区间的位移。由于这个区间是个文件映射区,这个位移就是在所映射文件“视图(View)”里面的位移。
我们继续往下看代码。
[MmAccessFault() > MmNotPresentFault() > MmNotPresentFaultSectionView()]
/*
* Get the entry corresponding to the offset within the section
*/
Entry = MmGetPageEntrySectionSegment(Segment, Offset);
if (Entry == 0)
{
/*
* If the entry is zero (and it can't change because we have
* locked the segment) then we need to load the page.
*/
/*
* Release all our locks and read in the page from disk
*/
MmUnlockSectionSegment(Segment);
MmUnlockAddressSpace(AddressSpace);
if ((Segment->Flags & MM_PAGEFILE_SEGMENT) ||
(Offset >= PAGE_ROUND_UP(Segment->RawLength) && Section->AllocationAttributes & SEC_IMAGE))
{
Status = MmRequestPageMemoryConsumer(MC_USER, TRUE, &Page);
if (!NT_SUCCESS(Status))
{
DPRINT1("MmRequestPageMemoryConsumer failed (Status %x)\n", Status);
}
}
else
{
Status = MiReadPage(MemoryArea, Offset, &Page);
if (!NT_SUCCESS(Status))
{
DPRINT1("MiReadPage failed (Status %x)\n", Status);
}
}
if (!NT_SUCCESS(Status))
{
/*
* FIXME: What do we know in this case?
*/
/*
* Cleanup and release locks
*/
MmLockAddressSpace(AddressSpace);
PageOp->Status = Status;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(Status);
}
/*
* Relock the address space and segment
*/
MmLockAddressSpace(AddressSpace);
MmLockSectionSegment(Segment);
/*
* Check the entry. No one should change the status of a page
* that has a pending page-in.
*/
Entry1 = MmGetPageEntrySectionSegment(Segment, Offset);
if (Entry != Entry1)
{
DPRINT1("Someone changed ppte entry while we slept\n");
KEBUGCHECK(0);
}
/*
* Mark the offset within the section as having valid, in-memory
* data
*/
Entry = MAKE_SSE(Page << PAGE_SHIFT, 1);
MmSetPageEntrySectionSegment(Segment, Offset, Entry);
MmUnlockSectionSegment(Segment);
Status = MmCreateVirtualMapping(AddressSpace->Process,
Address,
Attributes,
&Page,
1);
if (!NT_SUCCESS(Status))
{
DPRINT1("Unable to create virtual mapping\n");
KEBUGCHECK(0);
}
MmInsertRmap(Page, AddressSpace->Process, (PVOID)PAddress);
if (Locked)
{
MmLockPage(Page);
}
PageOp->Status = STATUS_SUCCESS;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_SUCCESS);
}
先通过 MmGetPageEntrySectionSegment()从映射段页面表中读出位移量 Offset 所对应的页面表顶 Entry 。
这个表项为0表示尚未建立通往目标页面的映射,所以由MiReadPage()根据Ofset 从目标文件读入目标页面。读入目标页面是个比较漫长的操作,中间还可能发生线程切换,所以完成了读入之后还要再确认映射段页面表中的表项并未发生变化,如果发生了变化就说明程序有问题。然后就把承载着目标页面的物理页面号以SSE的形式记录在映射段页面表中的相应表项里,SSE应该是Section Segment Entry”的缩写,宏操作 MAKE_SSEO定义为:
#define MAKE SSE(P, C) ((P)| ((C) << 1))
所以,物理页面号仍占高20位,因为PAGE_SHIFT定义为12。但是SSE的最低位为0,这表明目标页面在物理内存页面中,也表示SSE的内容是物理页面号。
接着就是通过 MmCreateVirtualMappingO建立页面映射,
建立起映射之后,该物理页面的内容有可能被“修剪”倒换到目标文件中。此时会将SSE的内容改成文件内部的位移。
#define MAKE SWAP SSE(S) (((S) << 1)10x1)
这里的参数S就是目标页面在文件内部的位移,即页面号。当然,这个位移可以根据具体表项的下标和记录在文件映射区和映射段数据结构中的信息推算出来。可见,最低位为1表示SSE的内容是文件内部的页面号。
回到MmNotPresentFaultSectionView()的代码,如果SSE非0就表示已经建立了文件页面的映射此时的SSE不是指向一个物理内存页面就是指向目标文件中的一个页面。但是可想而知此时一定是指向目标文件中的页面,要不然就不会发生页面异常了。
[MmAccessFault() > MmNotPresentFault( > MmNotPresentFaultSectionView()]
//SSE 非0
else if (IS_SWAP_FROM_SSE(Entry))
{
SWAPENTRY SwapEntry;
SwapEntry = SWAPENTRY_FROM_SSE(Entry);
/*
* Release all our locks and read in the page from disk
*/
MmUnlockSectionSegment(Segment);
MmUnlockAddressSpace(AddressSpace);
Status = MmRequestPageMemoryConsumer(MC_USER, TRUE, &Page);
if (!NT_SUCCESS(Status))
{
KEBUGCHECK(0);
}
Status = MmReadFromSwapPage(SwapEntry, Page);
if (!NT_SUCCESS(Status))
{
KEBUGCHECK(0);
}
/*
* Relock the address space and segment
*/
MmLockAddressSpace(AddressSpace);
MmLockSectionSegment(Segment);
/*
* Check the entry. No one should change the status of a page
* that has a pending page-in.
*/
Entry1 = MmGetPageEntrySectionSegment(Segment, Offset);
if (Entry != Entry1)
{
DPRINT1("Someone changed ppte entry while we slept\n");
KEBUGCHECK(0);
}
/*
* Mark the offset within the section as having valid, in-memory
* data
*/
Entry = MAKE_SSE(Page << PAGE_SHIFT, 1);
MmSetPageEntrySectionSegment(Segment, Offset, Entry);
MmUnlockSectionSegment(Segment);
/*
* Save the swap entry.
*/
MmSetSavedSwapEntryPage(Page, SwapEntry);
Status = MmCreateVirtualMapping(AddressSpace->Process,
Address,
Region->Protect,
&Page,
1);
if (!NT_SUCCESS(Status))
{
DPRINT1("Unable to create virtual mapping\n");
KEBUGCHECK(0);
}
MmInsertRmap(Page, AddressSpace->Process, (PVOID)PAddress);
if (Locked)
{
MmLockPage(Page);
}
PageOp->Status = STATUS_SUCCESS;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_SUCCESS);
}
else
{
/*
* If the section offset is already in-memory and valid then just
* take another reference to the page
*/
Page = PFN_FROM_SSE(Entry);
MmSharePageEntrySectionSegment(Segment, Offset);
MmUnlockSectionSegment(Segment);
//建立虚存页面郁物理页面之间的映射
Status = MmCreateVirtualMapping(AddressSpace->Process,
Address,
Attributes,
&Page,
1);
if (!NT_SUCCESS(Status))
{
DPRINT1("Unable to create virtual mapping\n");
KEBUGCHECK(0);
}//物理页面与目标进程之间的联系
MmInsertRmap(Page, AddressSpace->Process, (PVOID)PAddress);
if (Locked)
{
MmLockPage(Page);
}
PageOp->Status = STATUS_SUCCESS;
MmspCompleteAndReleasePageOp(PageOp);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_SUCCESS);
这里的IS_SWAP FROM SSEO和SWAPENTRY_FROM_SSEO定义为:
#define IS_SWAP_FROM_SSE(E) ((E) & 0x00000001)
#define SWAPENTRY_FROM_SSE(E) ((E) >> 1)
其余的代码就无须解释了