8.2. 共享内存

共享内存通常是进程间通讯最快的形式。 它提供一块在应用程序间共享的内存区域。 一个应用能够在另一个应用读取数据时写数据。

这样一块内存区用 Boost.Interprocess 的 boost::interprocess::shared_memory_object 类表示。 为使用这个类,需要包含 boost/interprocess/shared_memory_object.hpp 头文件。

  1. #include <boost/interprocess/shared_memory_object.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::interprocess::shared_memory_object shdmem(boost::interprocess::open_or_create, "Highscore", boost::interprocess::read_write);
  7. shdmem.truncate(1024);
  8. std::cout << shdmem.get_name() << std::endl;
  9. boost::interprocess::offset_t size;
  10. if (shdmem.get_size(size))
  11. std::cout << size << std::endl;
  12. }

boost::interprocess::shared_memory_object 的构造函数需要三个参数。 第一个参数指定共享内存是要创建或打开。 上面的例子实际上是指定了两种方式:用 boost::interprocess::open_or_create 作为参数,共享内存如果存在就将其打开,否则创建之。

假设之前已经创建了共享内存,现打开前面已经创建的共享内存。 为了唯一标识一块共享内存,需要为其指定一个名称,传递给 boost::interprocess::shared_memory_object 构造函数的第二个参数指定了这个名称。

第三个,也就是最后一个参数指示应用程序如何访问共享内存。 例子应用程序能够读写共享内存,这是因为最后的一个参数是 boost::interprocess::read_write

在创建一个 boost::interprocess::shared_memory_object 类型的对象后,相应的共享内存就在操作系统中建立了。 可是此共享内存区域的大小被初始化为0.为了使用这块区域,需要调用 truncate() 函数,以字节为单位传递请求的共享内存的大小。 对于上面的例子,共享内存提供了1,024字节的空间。

请注意,truncate() 函数只能在共享内存以 boost::interprocess::read_write 方式打开时调用。 如果不是以此方式打开,将抛出 boost::interprocess::interprocess_exception 异常。

为了调整共享内存的大小,truncate() 函数可以被重复调用。

在创建共享内存后,get_name()get_size() 函数可以分别用来查询共享内存的名称和大小。

由于共享内存被用于应用程序之间交换数据,所以每个应用程序需要映射共享内存到自己的地址空间上,这是通过 boost::interprocess::mapped_region 类实现的。

你也许有些奇怪,为了访问共享内存,要使用两个类。 是的,boost::interprocess::mapped_region 还能映射不同的对象到具体应用的地址空间。 如 Boost.Interprocess 提供 boost::interprocess::file_mapping 类实际上代表特定文件的共享内存。 所以 boost::interprocess::file_mapping 类型的对象对应一个文件。 向这个对象写入的数据将自动保存关联的物理文件上。 由于 boost::interprocess::file_mapping 不必加载整个文件,但却可以使用 boost::interprocess::mapped_region 将任意部分映射到地址空间,所以就能处理几个GB的文件,而这个文件在32位系统上是不能全部加载到内存上的。

  1. #include <boost/interprocess/shared_memory_object.hpp>
  2. #include <boost/interprocess/mapped_region.hpp>
  3. #include <iostream>
  4.  
  5. int main()
  6. {
  7. boost::interprocess::shared_memory_object shdmem(boost::interprocess::open_or_create, "Highscore", boost::interprocess::read_write);
  8. shdmem.truncate(1024);
  9. boost::interprocess::mapped_region region(shdmem, boost::interprocess::read_write);
  10. std::cout << std::hex << "0x" << region.get_address() << std::endl;
  11. std::cout << std::dec << region.get_size() << std::endl;
  12. boost::interprocess::mapped_region region2(shdmem, boost::interprocess::read_only);
  13. std::cout << std::hex << "0x" << region2.get_address() << std::endl;
  14. std::cout << std::dec << region2.get_size() << std::endl;
  15. }

为了使用 boost::interprocess::mapped_region 类,需要包含 boost/interprocess/mapped_region.hpp 头文件。 boost::interprocess::mapped_region 的构造函数的第一个参数必须是 boost::interprocess::shared_memory_object 类型的对象。 第二个参数指示此内存区域对应用程序来说,是只读或是可写的。

上面的例子创建了两个 boost::interprocess::mapped_region 类型的对象。 名为"Highscore"的共享内存,被映射到进程的地址空间两次。 通过 get_address()get_size() 这两个函数获得共享内存的地址和大小写到标准标准输出流中。 在这两种情况下,get_size() 的返回值都是1024,而 get_address() 的返回值是不同的。

下面的例子使用共享内存写入并读取一个数字。

  1. #include <boost/interprocess/shared_memory_object.hpp>
  2. #include <boost/interprocess/mapped_region.hpp>
  3. #include <iostream>
  4.  
  5. int main()
  6. {
  7. boost::interprocess::shared_memory_object shdmem(boost::interprocess::open_or_create, "Highscore", boost::interprocess::read_write);
  8. shdmem.truncate(1024);
  9. boost::interprocess::mapped_region region(shdmem, boost::interprocess::read_write);
  10. int *i1 = static_cast<int*>(region.get_address());
  11. *i1 = 99;
  12. boost::interprocess::mapped_region region2(shdmem, boost::interprocess::read_only);
  13. int *i2 = static_cast<int*>(region2.get_address());
  14. std::cout << *i2 << std::endl;
  15. }

通过变量 region, 数值 99 被写到共享内存的开始处。 然后变量 region2 访问共享内存的同一个位置,并将数值写入到标准输出流中。 正如前面例子的 getaddress() 函数的返回值所见,虽然变量 _regionregion2 表示的是该进程内不同的内存区域,但由于两个内存区域底层实际访问的是同一块共享内存,所以程序打印出99

通常,不会在同一个应用程序内使用多个 boost::interprocess::mapped_region 访问同一块共享内存。 实际上在同一个应用程序内将同一个共享内存映射到不同的内存区域上没有多大的意义,上面的例子只用于说明的目的。

为了删除指定的共享内存,boost::interprocess::shared_memory_object 对象提供了静态的 remove() 函数,此函数带有一个要被删除的共享内存名称的参数。

Boost.Interprocess 类的RAII概念支持,明显来自关于智能指针的章节,并使用了另外的一个类名称 boost::interprocess::remove_shared_memory_on_destroy。 它的构造函数需要一个已经存在的共享内存的名称。 如果这个类的对象被销毁了,那么在析构函数中会自动删除共享内存的容器。

请注意构造函数并不创建或打开共享内存,所以,这个类并不是典型RAII概念的代表。

  1. #include <boost/interprocess/shared_memory_object.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. bool removed = boost::interprocess::shared_memory_object::remove("Highscore");
  7. std::cout << removed << std::endl;
  8. }

如果 remove() 没有被调用, 那么,即使进程终止,共享内存还会一直存在,而不论共享内存的删除是否依赖底层操作系统。 多数Unix操作系统,包括Linux,一旦系统重新启动,都会自动删除共享内存,在 Windows 或 Mac OS X上,remove() 必须调用,这两种系统实际上将共享内存存储在持久化的文件上,此文件在系统重启后还是存在的。

Windows 提供了一种特别的共享内存,它可以在最后一个使用它的应用程序终止后自动删除。 为了使用它,提供了 boost::interprocess::windows_shared_memory 类,定义在 boost/interprocess/windows_shared_memory.hpp 文件中。

  1. #include <boost/interprocess/windows_shared_memory.hpp>
  2. #include <boost/interprocess/mapped_region.hpp>
  3. #include <iostream>
  4.  
  5. int main()
  6. {
  7. boost::interprocess::windows_shared_memory shdmem(boost::interprocess::open_or_create, "Highscore", boost::interprocess::read_write, 1024);
  8. boost::interprocess::mapped_region region(shdmem, boost::interprocess::read_write);
  9. int *i1 = static_cast<int*>(region.get_address());
  10. *i1 = 99;
  11. boost::interprocess::mapped_region region2(shdmem, boost::interprocess::read_only);
  12. int *i2 = static_cast<int*>(region2.get_address());
  13. std::cout << *i2 << std::endl;
  14. }

请注意,boost::interprocess::windows_shared_memory 类没有提供 truncate() 函数,而是在构造函数的第四个参数传递共享内存的大小。

即使 boost::interprocess::windows_shared_memory 类是不可移植的,且只能用于Windows系统,但使用这种特别的共享内存在不同应用之间交换数据,它还是非常有用的。