windows XP,ReactOS系统3.4 共享映射区(Section)---1

系列文章目录


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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值