系列文章目录
文章目录
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

最低0.47元/天 解锁文章
956

被折叠的 条评论
为什么被折叠?



