Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6266600
  • 博文数量: 2759
  • 博客积分: 1021
  • 博客等级: 中士
  • 技术积分: 4091
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-11 14:14
文章分类

全部博文(2759)

文章存档

2019年(1)

2017年(84)

2016年(196)

2015年(204)

2014年(636)

2013年(1176)

2012年(463)

分类: 信息化

2015-06-17 04:41:32

Memory Mapped Files
内存映像文件

What is a memory mapped file ?
什么是内存映像文件 ?

File mapping is the association of a file's contents with a portion of the address space of a process.
文件映像就是建立文件中所包含的内容与进程内存地址中的部分空间二者之间的联系

The system creates a file mapping to associate the file and the address space of the process.
系统创建文件映像的目的就是为了通过它建立文件与进程地址空间的联系

A mapped regsion is the portion of address that the process uses to access the file's contents .
映射域是(进程)地址的一部分,通过映射域可以在进程中访问(映像)文件中的内容

A single file mapping can have several mapped regsions,so that the user can associate parts of the file with the address space of the process without
mapping the entire file in the address space, since the file can be bigger than the whold address space of the process ( a 9GB DVD image file in a usual 32 bit systems).
就单个的映像文件而言,它可以被映射到多个映射域中。这样的话,使用者便可以将与之关联的文件段映射到其所在的进程地址空间中,而不是
把整个的映像文件映射到地址空间中,因为映像文件的大小有可能要远比整个进程地址空间要大(进程地址空间的大小 在常见 32 位系统中有一个 9GB 的DVD 视频文件那么大)

Processes read from and write to the file using pointers ,just like with dynamic memory .
同访问动态分配而创建的内存空间的方法一样,进程使用指针向映像文件中读取、写入数据

File mapping has the following advantages:
使用映像文件(来作为共享内存对象)有着如下的优点:

        Uniform resource use. File and memory can be treated using the same functions
        统一资源使用方式. 文件与内存可以通过相同的方法来访问.
    
        Automatic file data synchronization and cache from the OS.    
         操作系统会为文件提供自发的数据同步策略以及文件缓存机制(这些无需自己编程实现)
 
        Reuse of C++ utilities( STL containers, algorithms) in files.
        在操作文件对象的时候,可以重用 C++ 中的许多公共库函数(比如 STL 容器库,算法库都可以用在文件对象上)

        Shared memory between two or more applications.
         (使用映像文件作为共享内存对象的话)可以在两个或更多的应用进程间创建共享内存.
    
        Allows efficient work with a large files, without mapping the whole file into memory
        不将全部的文件映射到进程地址空间,这一特点提高将大文件作为共享内存对象的工作效率
    
        If several processes use the same file mapping to create mapped regions of a file , each process' views contain identical copies of the file on disk.
        如果多个进程将同一个文件映射到自己的进程地址空间并将其映射为映射域对象的话,(是不会将该文件多次存储的)每个进程所看到的全都是同一个来自于硬盘上的该映像文件的唯一的拷贝.
    
File mapping is not only used for interprocess communication, it can be used also to simplify file usage, so the user does not need to use file-management functions
write to the file.
文件映像不仅能用在进程间通信方面,它还可以被用在简化文件操作方面,这样一来使用者便无需通过使用文件管理方法来向文件中写数据了.

The user just writes data to the process memory, and the operating systems dumps the data to the file.       
用户仅可以通过将数据写入到进程内存中,随后操作系统便会将写入的数据转储到文件中.

When two processes map the same file in memory, the memory that one process writes is seen by another process, so memory mapped files can be used as an
interprocess communication mechanism.
当两个进程把同一个文件映射到其内存地址空间中时,其中一个进程向共享内存写入的数据对于另个一进程时可见的,所以创建内存映像文件可以被用作进程间通信
的一种机制

We can say that memory-mapped files offer the same interprocess communication services as shared memory with the addition of filesystem persistence.
我们可以得出这样的结论: 内存映像文件和共享文件一样可被用在进程通信服务中,除此之外,内存映像文件还具备有着与文件系统同样的生命周期, 正是因为这一点,
让写入到映像文件中的数据可被持久化.

However,as the operating system has to synchronize the file contents with the memory contents, memory-mapped files are not as fast as shared memory.
但是,由于操作系统不得不同步化文件与内存中的数据,这使得映像文件内存的读写速度远不及共享内存.



Using mapped files
使用映像文件

To use memory-mapped files, we have to perform 2 basic steps:
使用内存映像文件,我们需要执行下面两步基本的操作:

            Create a mappable object that represent an already created file of the fillesystem.
               创建可被映射的对象用来表示已经创建于文件系统中的文件

            This object will be used to create multiple mapped regions of the file.
              这个可被映射对象将被用作做,被多次映射到(不同进程中的) 由映射文件创建的映射域
    
               Associate the whole file or parts of the file with the address space of the calling process.
               建立需要映射的进程中的地址空间与整个文件或是部分文件之间的映射关系
     
               The operating system looks for a big enough memory address range in the calling process' address space and marks that address range as an spacial range.
               操作系统会在所有申请映射进程的内存地址中找出一个足够大的出来,然后将该地址区域标志为特殊的内存地址域

              Changes in that address range are automatically seen by other process that also have mapped the same file and those changes are also transferred to the disk
               automatically.
               在特殊地址域中的数值变化对其他将相同文件映射到自己地址域中的进程,是自动可见的,并且对该特殊地址域中数值的修改会被(操作系统)自动的转储到
               (该映像文件所存放的)硬盘中。

Once the two steps have been successfully completed, the process can start writing to and reading from the address space to send to and receive data from other process
and synchronize the file's contents with the changes made to the mapped region.
这两个步骤一经成功执行,进程便可开始从该地址空间中写入发送给其他进程的数据和读出来自于其他进程的数据信息了,同时还可以将映射域中数据的变化同步到
文件中.

Now,let's see how can we do this using Boost.Interprocess.
好的,那么我们来看看如何使用 Boost.Interprocess 函数库来实现上述的情景

Header
需要引入的头文件

To manage mapped files, you just need to include the following header.
想要操作映射文件,你仅需要把下面的头文件引入到自己的代码文件中.

#include

Creating a file mapping
创建映像文件

First , we have to link a file's contents with the process' address space.
首先,我们需要做的就是创建文件的内容与进程地址空间二者的链接关系

To do this, we have to create a mappable object that represents that file.
这么做的话,我们就要创建一个用来代表该文件的可映射的对象.

This is achieved in Boost.Interprocess creating a file_mapping object:
上述的这个功能在 Boost.Interprocess 库中已经被实现了,只需要创建一个 file_mapping 的实例对象即可:

using boost::interprocess ;

file_mapping m_file ("/user/home/file " ,  // filename 这个便是所要创建的映像文件的文件名称
                                read_write ,            // read-write mode 这个是映像文件允许被访问的模式
                                ) ;

Now we can use the newly create object to create mapped regions.
现在我们就可以使用新创建的对象来生成映射域了

Mapping File's Contents In Memory
将文件中的内容映射如内存中

After creating a file mapping, a process just has to map the shared memory in the process' address space.
在创建映像文件之后,进程便可以将这个映像文件作为共享内存对象映射到自己的进程地址空间中.

The user can map the whole shared memory or just part of it.
使用者(进程) 可以把整个或是其中的一部分文件映射到自己的进程地址空间中.

The mapping process is done using the mapped_region class , as we have said before .
映射的过程, 如我们之前所说的一样,通过创建mapped_region 类实例便可以实现

The class represents a memory region that has been mapped from a shared memory or from other devices that have also mapping capabilities:
mapped_region 这个类(抽象)表示的是: 从一个共享内存或是其他具有可映射性的设备中提取出来的一块内存区域

using boost::interprocess ;
std::size_t FileSize = ...

// Map the second half of the file
mapped_region region
(
     m_file ,                         // Memory-mappable object   内存-可映射 对象
     read_write ,                   // Access mode                    访问内存映射对象的模式
     FileSize / 2 ,                 // Offset from the beginning of the shm                   从内存-可映射对象起始位置多少字节开始映射
     FileSize - FileSize/2  ,     // length of the region                                从内存-可映射 中映射多少个自己额
) ;

// Get the address of the region
// 该方法 get_address () 来自于 mapped_region 类,用来获取映射域中的起始地址 
region.get_address () ;

// get the size of the region
// get_size()  方法来自于 mapped_region 类,用于获取映射域所对应的映射对象的字节个数
// 对应于 mapped_region 类构造方法中的第四个参数
region.get_size() ;

The user can specify the offset from the file where the mapped region should start and the size of the mapped region .
使用者可以通过设置 offset 和 size 参数来指定将映像文件从哪里开始映射到映射域中以及将映像文件从起始位置开始向映射域中总共映射多少个字节.

If no offset or size is specified , the whole file is mapped.
如果使用者没有指定 offset 和 size 的数值,默认操作便是将整个文件全部映射到映射域中

If the offset is specified,but not the size, the mapped region covers from the offset until the end of the file.
如果仅指定了 offset (起始位置) 但是没有指定 size(总共设定多少个字节), 映射域将从设定映像文件的 起始位置开始将后续直到文件结束的所有字节
全部映射到映射域中.

If several processes map the same file, and a process modifies a memory range from a mapped region that is also mapped by other process, the changes are
immediately visible to other processes.
如果有多个进程将同样的文件映射到自己的地址空间中,其中一个进程通过映射域修改了内存中的内容,这段被修改的内存同样也会被映射到其他的进程中,
改动的部分对剩余的进程是立即可见的.(这种变动是实时可见的,不存在刷新或是延迟时间)

However, the file contents on disk are not updated immediately, since that would hurt performance ( writing to disk is several times slower than writing to memory).
然而,内存中文件内容的变动是不会立即更新到存放该文件的硬盘上的,因为立即刷新存放在硬盘文件数据的话会影响到程序的性能(刷新硬盘要比刷新内存多花几倍的时间)

If the user wants to make sure that file's contents have been updated,it can flush a range from the view to disk.
如果使用者像确保文件内容的更新已经及时写入到该文件存放的硬盘上,它可以通过调用 flush 方法来将内存中某一区域上的数据,来将位于这一区间上的数据更新到磁盘上

// Flush the whole region
// 将整个映射域中变动的数据,刷新到硬盘中
region.flush() ;

// Flush from an offset until the end of the region
// 从 offset 标定的字段开始直到 映射域结束 这一区间上的数据的变动 刷新到硬盘中
region.flush(offset) ;

// Flush a memory range starting on an offset
// 通过指定起始点和字节长度,来将这一区域上变动刷新到硬盘上
// 变化域是 [offset, offset+size)
region.flush( offset, size);

Remember that the offset is not an offset on the file , but an offset in the mapped region.
需要谨记的是,flush 参数中的 offset 数值指的并不是 映像文件中的起始地址 而是 映射域 中的起始地址

If a region covers the second half of a file and flushes the whole region, only the half of the file is guaranteed to have been flushed.
如果映射域中包含的是半个映射文件,那么执行映射域的整体刷新操作的话,仅仅会使得硬盘上的映像文件一半内容得到刷新。

For more details regarding mapped_region see the class reference.
想要知道 mapped_region 更多的细节信息的话,参看 类参考资料



A Simple Example 
简单实例

Let's reproduce the same example described in the shared memory section ,  using memory mapped files.
让我们通过使用内存映像文件来再次重新描述一下在 共享内存哪一张节中所实现的例子吧

A server process creates a shared memory segment, maps it and initializes all the bytes to a value .
服务进程创建了一个共享内存段,并将其映射到映射域中,并通过映射域对象将内存段中的所有字节全部初始化成某一个数值

After that , a client process opens the shared memory, maps it , and checks that the data is correctly initialized :
在此之后,客户进程打开共享内存,将其映射到自己进程的地址空间中,并检查共享内存段中的每个字节是否被正确的初始化.

点击(此处)折叠或打开

  1. #include <boost/interprocess/file_mapping.hpp>
  2. #include <boost/interprocess/mapped_region.hpp>
  3. #include <iostream>
  4. #include <fstream>
  5. #include <string>
  6. #include <vector>
  7. #include <cstring>
  8. #include <cstddef>
  9. #include <cstdlib>

  10. int main(int argc, char *argv[])
  11. {
  12.    using namespace boost::interprocess;

  13.    //Define file names
  14.   // 首先设定文件名称,创建文件对象作为生成映射域
  15.   // 的共享内存对象

  16.    const char *FileName = "file.bin";
  17.    const std::size_t FileSize = 10000;

  18.    if(argc == 1){ //Parent process executes this
  19.       { //Create a file
  20.         // 下面的 if 分支下面的代码是 server 进程将要执行的
  21.        //
  22.        // 为了防止在 server 进程之前就有其他进程使用同名的文件作为共享对象创建 映射域
  23.        // 造成此次创建映射域的错误,首先删除同名文件所创建的映射域
  24.          file_mapping::remove(FileName);
  25. //    
  26. //        随后创建 filebuf 文件缓冲对象,并通过文件缓冲类中的 open 方法将文件以
  27. // 读模式写模式,如果不能存在则创建同名文件和二进制文件模式 打开
  28.          std::filebuf fbuf;
  29.          fbuf.open(FileName, std::ios_base::in | std::ios_base::out
  30.                               | std::ios_base::trunc | std::ios_base::binary);
  31.          //Set the size
  32.          // 将文件位移指针 以文件的起始位置开始移动指向文件中最后一个字节上
  33.          fbuf.pubseekoff(FileSize-1, std::ios_base::beg);
  34.    
  35.         // 将文件中最后一个字节的数据置为数值 0
  36.          fbuf.sputc(0);
  37.       }

  38.       //Remove on exit
  39.      // 创建封装了 移除映像文件操作的 结构体
  40.     // 结构体的构造函数 用于为结构体中的 FileName_ 变量进行赋值操作
  41.    // 而结构体的析构函数 用于将指定名称的文件对应的共享内存移除
  42.       struct file_remove
  43.       {
  44.          file_remove(const char *FileName)
  45.             : FileName_(FileName) {}
  46.          ~file_remove(){ file_mapping::remove(FileName_); }
  47.          const char *FileName_;
  48.       } remover(FileName);

  49.       //Create a file mapping
  50.       // 基于刚刚创建的映像文件进行映射, m_file 便是基于该映像文件生成的共享内存对象
  51.       file_mapping m_file(FileName, read_write);

  52.       //Map the whole file with read-write permissions in this process
  53.     // 在本进程中将全部的共享内存对象映射到映射域中
  54.       mapped_region region(m_file, read_write);

  55.       //Get the address of the mapped region
  56.      // 通过生成的映射域对象,可以获取该共享内存的起始地址 和 该共享内存的大小(字节)
  57.       void * addr = region.get_address();
  58.       std::size_t size = region.get_size();

  59.       //Write all the memory to 1
  60.       // 将共享内存中的所有字节均置为数值 1
  61.       std::memset(addr, 1, size);

  62.       //Launch child process
  63.      // 接下来运行子进程,将运行命令从之前的 ./demo ---> 修改为 ./demo child
  64.      // argc 从 1 ---> 2 导致 if(argc != 1 ) 从而走的是 else 分支

  65.       std::string s(argv[0]); s += " child ";
  66.       if(0 != std::system(s.c_str()))
  67.          return 1;
  68.    }
  69.    else{ //Child process executes this
  70.       { //Open the file mapping and map it as read-only
  71.         //
  72.         // 下面的代码是 子进程 执行的
  73.         // 首先是创建 file_mapping 实例
  74.          file_mapping m_file(FileName, read_only);
  75.        //
  76.        // 紧接着使用 m_file 实例作为共享内存实体 映射生成映射域 对象
  77.          mapped_region region(m_file, read_only);

  78.          //Get the address of the mapped region
  79.          // 有了映射域便可以方便的对共享内存施加操作
  80.         // 通过 get_address () 方法来获取共享内存中的起始地址,通过 get_size() 来获取共享空间的大小(字节)

  81.          void * addr = region.get_address();
  82.          std::size_t size = region.get_size();

  83.          //Check that memory was initialized to 1
  84.          const char *mem = static_cast<char*>(addr);
  85.          for(std::size_t i = 0; i < size; ++i)
  86.             if(*mem++ != 1)
  87.                return 1; //Error checking memory
  88.       }
  89.       { //Now test it reading the file
  90.         // 下面的这个分支是前面几个示例代码中所不包含的,这个是因为共享内存创建所基于的实体是文件
  91.         // 而文件是可以被持久化存放到磁盘上面的,所以涉及到读入到内存中的文件在被修改之后,如何,何时才会将内存中的改变刷新到硬盘上这些问题
  92. //
  93. // 而下面的这些代码便是验证,上述操作之后,作为共享内存实体的映像文件所存放的文件中的内容是否发生了改变
  94. //
  95. //        首先将硬盘上存放的文件读入到当前(子进程) 的内存中
  96.          std::filebuf fbuf;
  97.          fbuf.open(FileName, std::ios_base::in | std::ios_base::binary);

  98.          //Read it to memory
  99.         // 然后,创建一个用来缓冲文件中的数据,容纳字节个数=文件长度 的char 类型的 vector 对象
  100.          std::vector<char> vect(FileSize, 0);
  101. //
  102. // 随之调用 filebuf 中的 sgetn 方法将文件中的内容依次读入到 vector 中, 话说这里的 sgetn , streamsize 我还是从来没有用过
  103.          fbuf.sgetn(&vect[0], std::streamsize(vect.size()));

  104.          //Check that memory was initialized to 1
  105.         //
  106.         // 接下来的操作也是遍历 vecto 中的所有元素看看是否是每个字节的数值与 共享空间在 server 进程初始化的数值一样都是数值 1
  107.          const char *mem = static_cast<char*>(&vect[0]);
  108.          for(std::size_t i = 0; i < FileSize; ++i)
  109.             if(*mem++ != 1)
  110.                return 1; //Error checking memory
  111.       }
  112.    }

  113.    return 0;
  114. }


to be continued
阅读(1945) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~