七. 托管内存片段
使进程间数据通信变容易 托管共享内存 托管映射文件 托管内存片段的特性 托管内存片段的高级特性 托管堆内存和托管外部缓冲区 |
使进程间数据通信变容易
介绍 声明托管内存片段类 |
介绍
正如我们所看到的,Boost.Interprocess提供一些基本的类来构建共享内存对象和文件映射,然后映射这些可映射的类至进程的地址空间。
然而,管理这些内存片段对一些特殊任务来说没那么容易。一个映射区域是一个固定长度的内存缓冲区,能够动态的创建和销毁任意类型的对象,这需要非常多的工作量,因为这需要为那部分内存编写一个内存管理算法。很多时候,我们还想在共享内存上关联对象和其名称,以便所有进程能通过名称找到对象。
Boost.Interprocess提供4个托管内存片段类:
- 用于管理共享内存映射区域(basic_managed_shared_memory 类)。
- 用于管理内存映射文件(basic_managed_mapped_file类)。
- 用于管理在堆上分配(运算符new)的内存缓冲区(basic_managed_heap_memory类)。
- 用于管理一个用户提供的固定大小缓冲区(basic_managed_external_buffer类)。
前两个类管理能在进程间共享的内存片段。第三个类用于创建复杂的基数据,它通过其他机制例如消息队列发送至其他进程。第四个类能管理任意固定大小的内存缓冲区。前两个类将在下一小节介绍。
basic_managed_heap_memory 和 basic_managed_external_buffer将稍后做解释。
托管内存段的最重要的服务是:
- 动态分配内存段的部分。
- 在内存段中构建C++对象。这些对象可以是匿名的或我们可以为其关联一个名称。
- 具名对象的搜索能力。
- 许多特性的定制:内存分配算法,索引类型或字符类型。
- 原子构造和析构,以便如果内存段在两个进程间共享,我们能够创建两个关联同一名称的对象,从而简化同步。
声明托管内存片段类
所有的Boost.Interprocess托管内存片段类都是模板类,可以由用户定制。
template
<
class CharType,
class MemoryAlgorithm,
template<class IndexConfig> class IndexType
>
class basic_managed_shared_memory / basic_managed_mapped_file /
basic_managed_heap_memory / basic_external_buffer;
这些类可以使用以下的模板参数定制:
- CharType是一种字符类型,它被用于分辨已创建的具名对象(例如,char或wchar_t)。
-
MemoryAlgorithm 是内存算法,它被用于分配内存段部分(例如,rbtree_best_fit)。内部的内存算法也定义了:
- 同步类型(MemoryAlgorithm::mutex_family)被用在所有的分配操作中。它允许使用用户自定义互斥量或者避免内部锁定(可能代码需要用户进行外部同步)。
- 指针类型(MemoryAlgorithm::void_pointer)被用在内存分配算法或其他帮助结构中(例如一个用来维持对象/名称关联的映射表(map))。所有使用在此托管内存段中的STL兼容分配器和容器将使用此指针类型。指针类型将被定义如果托管内存能在几个进程间被映射。例如,如果void_pointer是offset_ptr<void> ,我们就能够映射托管片段至各进程不同的基地址上。如果void_pointer是void*,则仅固定地址映射可以使用。
- 参考 Writing a new memory allocation algorithm 以获取更多关于内存算法的详情。
- IndexType 是一种索引类型,它被用于存储名称-对象关联(例如,映射表(map)、哈希映射表(hash-map)或有序向量(orderd vector))。
这样,我们能使用char或wchar_t字符串来分辨在内存片段中创建的C++对象,我们能够插入新的共享内存分配算法,并且使用最合适的索引类型。
托管共享内存
常见的托管共享内存类 构建托管共享内存 使用原生Windows共享内存 使用XSI(系统V)共享内存 |
常见的托管共享内存类
如所示,basic_managed_shared_memory提供了多种定制方式。但对一般用户而言,需要一个常用的、缺省的共享内存具名对象创建。基于此原因,Boost.Interprocess定义了最常用的托管共享内存特例:
//!Defines a managed shared memory with c-strings as keys for named objects,
//!the default memory algorithm (with process-shared mutexes,
//!and offset_ptr as internal pointers) as memory allocation algorithm
//!and the default index type as the index.
//!This class allows the shared memory to be mapped in different base
//!in different processes
typedef
basic_managed_shared_memory<char
,/*Default memory algorithm defining offset_ptr<void> as void_pointer*/
,/*Default index type*/>
managed_shared_memory;
//!Defines a managed shared memory with wide strings as keys for named objects,
//!the default memory algorithm (with process-shared mutexes,
//!and offset_ptr as internal pointers) as memory allocation algorithm
//!and the default index type as the index.
//!This class allows the shared memory to be mapped in different base
//!in different processes
typedef
basic_managed_shared_memory<wchar_t
,/*Default memory algorithm defining offset_ptr<void> as void_pointer*/
,/*Default index type*/>
wmanaged_shared_memory;
managed_shared_memory在共享内存上分配关联c字符串的对象,wmanaged_shared_memory在共享内存上分配关联以wchar_t空字符结尾的字符串的对象。它们都定义了指针类型offset_ptr<void>,因此它们能被用来映射共享内存至不同进程的不同基地址上。
如果用户想映射共享内存至所有进程的相同地址上,并且想使用原始内部指针来代替偏移指针,Boost.Interprocess定义了如下类型:
//!Defines a managed shared memory with c-strings as keys for named objects,
//!the default memory algorithm (with process-shared mutexes,
//!and offset_ptr as internal pointers) as memory allocation algorithm
//!and the default index type as the index.
//!This class allows the shared memory to be mapped in different base
//!in different processes*/
typedef basic_managed_shared_memory
<char
,/*Default memory algorithm defining void * as void_pointer*/
,/*Default index type*/>
fixed_managed_shared_memory;
//!Defines a managed shared memory with wide strings as keys for named objects,
//!the default memory algorithm (with process-shared mutexes,
//!and offset_ptr as internal pointers) as memory allocation algorithm
//!and the default index type as the index.
//!This class allows the shared memory to be mapped in different base
//!in different processes
typedef basic_managed_shared_memory
<wchar_t
,/*Default memory algorithm defining void * as void_pointer*/
,/*Default index type*/>
wfixed_managed_shared_memory;
构建托管共享内存
托管共享内存是一个高级类,它结合了共享内存对象和覆盖所有共享内存对象的映射区域。那意味着如果我们创建了一个新的托管共享内存:
- 一个新的共享内存对象被创建。
- 全部共享内存对象被映射至进程的地址空间。
- 一些帮助对象被构建(具名对象的索引、内部同步对象、内部变量等)在共享区域上,用于执行托管内存段功能。
当我们打开一个托管内存段:
- 一个共享对象对象被打开。
- 全部共享内存对象被映射至进程的地址空间。
要使用一个托管共享内存,你必须包含以下头文件:
#include <boost/interprocess/managed_shared_memory.hpp>
//1. Creates a new shared memory object
// called "MySharedMemory".
//2. Maps the whole object to this
// process' address space.
//3. Constructs some objects in shared memory
// to implement managed features.
//!! If anything fails, throws interprocess_exception
//
managed_shared_memory segment ( create_only
, "MySharedMemory" //Shared memory object name
, 65536); //Shared memory object size in bytes
//1. Opens a shared memory object
// called "MySharedMemory".
//2. Maps the whole object to this
// process' address space.
//3. Obtains pointers to constructed internal objects
// to implement managed features.
//!! If anything fails, throws interprocess_exception
//
managed_shared_memory segment (open_only, "MySharedMemory");//Shared memory object name
//1. If the segment was previously created
// equivalent to "open_only" (size is ignored).
//2. Otherwise, equivalent to "create_only"
//!! If anything fails, throws interprocess_exception
//
managed_shared_memory segment ( open_or_create
, "MySharedMemory" //Shared memory object name
, 65536); //Shared memory object size in bytes
当对象managed_shared_memory被销毁,共享内存对象自动被取消映射,并且所有资源被释放。为了在系统中删除共享内存对象,你必须使用shared_memory_object::remove函数。共享内存对象的删除可能会失败,如果有其他进程仍旧拥有映射的共享内存对象。
用户也可以映射托管共享内存至固定的地址上。当使用fixed_managed_shared_memory时,这是必需的。为达此目的,仅需在一个额外的参数上添加映射地址:
fixed_managed_shared_memory segment (open_only ,"MyFixedAddressSharedMemory" //Shared memory object name
,(void*)0x30000000 //Mapping address
使用原生Windows共享内存
Windows用户可能想使用原生windows共享内存来替代可移植的shared_memory_object托管内存。可以通过类basic_managed_windows_shared_memory 达到此目的。要使用它,仅需包含:
#include <boost/interprocess/managed_windows_shared_memory.hpp>
此类具有与 basic_managed_shared_memory 相同的接口但使用的是原生windows共享内存。注意到此托管类具有与windows共享内存相同的生命周期问题:当最后一个关联windows共享内存的进程与此内存分离了(或结束/崩溃),此内存将被销毁。因此windows共享内存没有持久性支持。
要使用managed_windows_shared_memory在系统服务和用户应用间通信,请阅读章节 Native windowsshared memory中的解释。
使用XSI(系统V)共享内存
Unix用户可能也想使用XSI(系统V)来代替可移植的shared_memory_object托管内存。可以通过类basic_managed_xsi_shared_memory 达到此目的。要使用它,仅需包含:
#include <boost/interprocess/managed_xsi_shared_memory.hpp>
此类具有和 basic_managed_shared_memory 几乎相同的接口,但使用的是XSI共享内存做为后端。
更多关于操作XSI共享内存能力的信息,请参考 basic_managed_xsi_shared_memory
托管映射文件
常见的托管映射文件 构建托管映射文件 |
常见的托管映射文件
如所示,basic_managed_mapped_file提供了多种定制方式。但对一般用户而言,需要一个常用的、缺省的共享内存具名对象创建。基于此原因,Boost.Interprocess定义了最常用的托管映射文件特例:
//Named object creation managed memory segment
//All objects are constructed in the memory-mapped file
// Names are c-strings,
// Default memory management algorithm(rbtree_best_fit with no mutexes)
// Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_mapped_file <
char,
rbtree_best_fit<mutex_family, offset_ptr<void> >,
flat_map_index
> managed_mapped_file;
//Named object creation managed memory segment
//All objects are constructed in the memory-mapped file
// Names are wide-strings,
// Default memory management algorithm(rbtree_best_fit with no mutexes)
// Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_mapped_file<
wchar_t,
rbtree_best_fit<mutex_family, offset_ptr<void> >,
flat_map_index
> wmanaged_mapped_file;
managed_mapped_file在内存映射文件上分配关联c字符串的对象,wmanaged_mapped_file在内存映射文件上分配关联以wchar_t空字符结尾的字符串的对象。它们都定义了指针类型offset_ptr<void>,因此它们能被用来映射映射文件至不同进程的不同基地址上。
构建托管映射文件
托管映射文件是一个高级类,它结合了文件和覆盖所有文件的映射区域。那意味着如果我们创建了一个新的托管映射文件:
- 一个新文件被创建。
- 全部文件被映射至进程的地址空间。
- 一些帮助对象被构建(具名对象的索引、内部同步对象、内部变量等)在映射区域上,用于执行托管内存段功能。
当我们打开一个托管映射文件
- 一个文件被打开。
- 全部文件被映射至进程的地址空间。
要使用一个托管映射文件,你必须包含以下头文件:
#include <boost/interprocess/managed_mapped_file.hpp>
//1. Creates a new file
// called "MyMappedFile".
//2. Maps the whole file to this
// process' address space.
//3. Constructs some objects in the memory mapped
// file to implement managed features.
//!! If anything fails, throws interprocess_exception
//
managed_mapped_file mfile (create_only, "MyMappedFile", //Mapped file name 65536); //Mapped file size
//1. Opens a file
// called "MyMappedFile".
//2. Maps the whole file to this
// process' address space.
//3. Obtains pointers to constructed internal objects
// to implement managed features.
//!! If anything fails, throws interprocess_exception
//
managed_mapped_file mfile (open_only, "MyMappedFile"); //Mapped file name[c++]
//1. If the file was previously created
// equivalent to "open_only".
//2. Otherwise, equivalent to "open_only" (size is ignored)
//
//!! If anything fails, throws interprocess_exception
//
managed_mapped_file mfile (open_or_create, "MyMappedFile", //Mapped file name 65536); //Mapped file size
当对象managed_mapped_file被销毁,文件自动被取消映射,并且所有资源被释放。为了在文件系统中删除文件,你必须使用标准的C函数 std::remove或Boost.Filesystem的remove() 函数。但是文件的删除可能会失败,如果有其他进程仍旧拥有映射至内存上的文件或文件正被其他进程打开。
要获得移植性更高的行为,可以使用file_mapping::remove(const char *)操作,它将删除文件即使文件正被映射着。然而,在一些操作系统下,如果没有文件删除权限(例如C++文件流),删除将失败。但在多数情况下,file_mapping::remove具有足够高的移植性。
更多关于操作映射文件能力的信息,请参考 basic_managed_mapped_file。
托管内存片段的特性
支配托管内存片段 获取句柄以辨别数据 对象构造函数族 匿名实例构建 单例构建 同步保障 名称/对象映射表索引类型 片段管理 获取构建对象的信息 原子执行对象函数 |
以下特征对所有的托管内存段类都是常见的,但我们将在例子中使用托管共享内存。使用内存映射文件或其他托管内存段类,我们能达到相同的效果。
支配托管内存片段
如果需要从托管内存段(例如,托管共享内存)分配基本的原始字节,用于执行*进程间通信,此类提供了分配和释放函数。分配函数有抛异常和不抛异常的版本。如果内存不够,抛异常版本将抛 boost::interprocess::bad_alloc(派生自std::bad_alloc异常),不抛异常版本返回空指针。
#include <boost/interprocess/managed_shared_memory.hpp>
int main()
{
using namespace boost::interprocess;
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;
//Managed memory segment that allocates portions of a shared memory
//segment with the default management algorithm
managed_shared_memory managed_shm(create_only,"MySharedMemory", 65536);
//Allocate 100 bytes of memory from segment, throwing version
void *ptr = managed_shm.allocate(100);
//Deallocate it
managed_shm.deallocate(ptr);
//Non throwing version
ptr = managed_shm.allocate(100, std::nothrow);
//Deallocate it
managed_shm.deallocate(ptr);
return 0;
}
获取句柄以辨别数据
此类也能提供在托管内存段绝对地址与能被任何进程间通信机制使用的句柄间的转换。当使用包含此对象的托管内存段,此句柄能被再次转化为一个绝对地址。句柄能在进程间当关键字使用,用于辨别托管内存段的已分配部分或构建在托管片段上的对象。
//Process A obtains the offset of the address
managed_shared_memory::handle handle =
segment.get_handle_from_address(processA_address);
//Process A sends this address using any mechanism to process B
//Process B obtains the handle and transforms it again to an address
managed_shared_memory::handle handle = ...
void * processB_address = segment.get_address_from_handle(handle);
对象构造函数族
当在托管内存段(托管共享内存、托管映射文件等)上构件关联一个名字的对象时,用户有多种对象构建族用于“构件”或“如果未找到,则构建”。Boost.Interprocess能构建单个对象或一组对象。可以采用相同的参数构建对象组或通过迭代器列表定义各自的参数:
//!Allocates and constructs an object of type MyType (throwing version)
MyType *ptr = managed_memory_segment.construct<MyType>("Name") (par1, par2...);
//!Allocates and constructs an array of objects of type MyType (throwing version)
//!Each object receives the same parameters (par1, par2, ...)
MyType *ptr = managed_memory_segment.construct<MyType>("Name")[count](par1, par2...);
//!Tries to find a previously created object. If not present, allocates
//!and constructs an object of type MyType (throwing version)
MyType *ptr = managed_memory_segment.find_or_construct<MyType>("Name") (par1, par2...);
//!Tries to find a previously created object. If not present, allocates and
//!constructs an array of objects of type MyType (throwing version). Each object
//!receives the same parameters (par1, par2, ...)
MyType *ptr = managed_memory_segment.find_or_construct<MyType>("Name")[count](par1, par2...);
//!Allocates and constructs an array of objects of type MyType (throwing version)
//!Each object receives parameters returned with the expression (*it1++, *it2++,... )
MyType *ptr = managed_memory_segment.construct_it<MyType>("Name")[count](it1, it2...);
//!Tries to find a previously created object. If not present, allocates and constructs
//!an array of objects of type MyType (throwing version). Each object receives
//!parameters returned with the expression (*it1++, *it2++,... )
MyType *ptr = managed_memory_segment.find_or_construct_it<MyType>("Name")[count](it1, it2...);
//!Tries to find a previously created object. Returns a pointer to the object and the
//!count (if it is not an array, returns 1). If not present, the returned pointer is 0
std::pair<MyType *,std::size_t> ret = managed_memory_segment.find<MyType>("Name");
//!Destroys the created object, returns false if not present
bool destroyed = managed_memory_segment.destroy<MyType>("Name");
//!Destroys the created object via pointer
managed_memory_segment.destroy_ptr(ptr);
所有这些函数都有不抛出异常的版本,通过额外参数std::nothrow指定。例如,对简单对象构建:
//!Allocates and constructs an object of type MyType (no throwing version)
MyType *ptr = managed_memory_segment.construct<MyType>("Name", std::nothrow) (par1, par2...);
匿名实例构建
有时,用户不想创建关联名字的对象。基于此目的,Boost.Interprocess能在托管内存段上构建匿名对象。所有具名对象构建函数都可用于构建匿名对象。为构建一个匿名对象,用户必须使用名称"boost::interprocess::anonymous_instance" 代替一般名称:
MyType *ptr = managed_memory_segment.construct<MyType>(anonymous_instance) (par1, par2...);
//Other construct variants can also be used (including non-throwing ones)
...
//We can only destroy the anonymous object via pointer
managed_memory_segment.destroy_ptr(ptr);
查找函数在这里没有任何意义,因为匿名对象没有名称。我们只能通过指针来销毁匿名对象。
单例构建
有时,用户想在托管内存段上模拟一个单例。显然,由于托管内存段是在运行时构建的,因此用户必须显式的构建和销毁此对象。但是用户如何保证此对象是此托管内存段上的此类型唯一对象呢?可以使用一个具名对象来模拟,并检查在尝试创建一个前,它是否存在,但是所有进程必须认同对象的名称,这可能导致与其他已存在名字的冲突。
为解决此问题,Boost.Interprocess提供了在托管内存段中创建一个“唯一对象”。使用此“唯一对象”服务,仅有类的一个实例能够创建在托管内存段中(尽管你可以创建此类的多个具名对象),因此模拟进程间类单例对象变得更容易了,例如设计一个汇集的、共享内存分配器。此对象能够使用此类的类型做为关键字进行查找。
// Construct
MyType *ptr = managed_memory_segment.construct<MyType>(unique_instance) (par1, par2...);
// Find it
std::pair<MyType *,std::size_t> ret = managed_memory_segment.find<MyType>(unique_instance);
// Destroy it
managed_memory_segment.destroy<MyType>(unique_instance);
// Other construct and find variants can also be used (including non-throwing ones)
//...
// We can also destroy the unique object via pointer
MyType *ptr = managed_memory_segment.construct<MyType>(unique_instance) (par1, par2...);
managed_shared_memory.destroy_ptr(ptr);
查找函数获得具有类型T的唯一对象指针,它使用“唯一对象”机制创建。
同步保障
具名/唯一的分配/查找/析构的一个特征是它们都是原子的。具名分配器使用递归同步方案,此方案由内部互斥量族定义的内存分配算法模板参数(MemoryAlgorithm)指定。也即,用于同步具名/唯一分配器的互斥量类型是由类型MemoryAlgorithm::mutex_family::recursive_mutex_type定义的。对基于托管片段的共享内存和内存映射文件,此递归互斥量被定义为interprocess_recursive_mutex.
如果两个进程能同时调用:
MyType *ptr = managed_shared_memory.find_or_construct<MyType>("Name")[count](par1, par2...);
但只有一个进程能创建此对象,另外一个将获得已创建对象的指针。
当执行具名/匿名/唯一分配时,使用allocate()的原始分配器也能安全被调用,就好像编制一个多线程应用,此应用插入一个对象在互斥量保护的映射表中,当映射线程正在搜索插入新对象的位置时通过调用new[]而不阻塞其他线程。一旦映射表找到了正确的位置,并且它必须分配原始内存来构建新值时,同步过程确实发生了。
这意味着如果我们正在创建或查找大量的具名对象时,我们仅阻塞来自其他进程的创建/查找,但如果只是插入元素至共享内存数组中时,我们不阻塞其他进程。
名称/对象映射表索引类型
如上所示,当创建具名对象时,托管内存段存储名称/对象关联至索引中。此索引是一个映射表,它使用对象的名字做键,指向对象的指针做值。默认实例managed_shared_memory 和 wmanaged_shared_memory使用flat_map_index做为索引类型。
每个索引都有其自身的特性,例如查找-时间、插入时间、删除时间、内存使用和内存分配模式。Boost.Interprocess目前提供3种索引类型:
- boost::interprocess::flat_map_index flat_map_index:基于boost::interprocess::flat_map,一个有序的向量类似于Loki 库的AssocVector 类,提供大量的搜索时间和最小的内存使用。但是当填满时向量必须重分配,因此所有的数据都必须拷贝至新的缓冲区。最理想的使用情况是插入主要在初始化的时候进行,且在运行时我们仅需要查找。
- boost::interprocess::map_index map_index:基于boost::interprocess::map,一个std::map托管内存的可用版本。因为它是一个基于节点的容器,它不存在重分配问题,仅仅是树有时需要重调整一下。它提供了平衡的插入/删除/查找时间,相对于boost::interprocess::flat_map_index,它的每个节点需要更多的开销。最理想的使用情况是查找/插入/删除是随机的情况下。
- boost::interprocess::null_index null_index:这种索引是为使用托管内存段于原始内存缓冲区分配器的人准备,它们不使用命名/唯一分配器。此类为空,从而节省一些空间和编译时间。如果你尝试使用此索引来创建带托管内存段的具名对象,将产生编译错误。
做为一个例子,如果我们想定义使用boost::interprocess::map 为索引类型的新托管共享内存类,我们仅需指定 [boost::interprocess::map_indexmap_index] 做为模板参数。
//This managed memory segment can allocate objects with:
// -> a wchar_t string as key
// -> boost::interprocess::rbtree_best_fit with process-shared mutexes
// as memory allocation algorithm.
// -> boost::interprocess::map<...> as the index to store name/object mappings
//
typedef boost::interprocess::basic_managed_shared_memory
< wchar_t
, boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family, offset_ptr<void> >
, boost::interprocess::map_index
> my_managed_shared_memory;
一旦此容器被引入至Boost,Boost.Interprocess计划提供一个基于unordered_map的索引。如果这些索引对你来说还不够,你可以定义你自己的索引类型。欲了解如何做到这点,参考Building customindexes 章节。
片段管理
所有的Boost.Interprocess托管内存段类都在它们自己的内存片段(共享内存、内存映射文件、堆内存等)中构建一些结构来执行内存管理算法、具名分配器、同步对象等。所有这些对象都封装在一个叫做片段管理(segment manager)的单例对象中。一个托管内存映射文件和托管共享内存使用相同的片段管理来执行所有的托管内存段特性,这主要基于片段管理是一个管理固定大小内存缓冲区的类的事实。由于共享内存或内存映射文件都是通过映射区域访问,并且映射区域是一个固定大小的缓冲区,因此单例片段管理类能够管理多种托管内存段类型。
一些Boost.Interprocess类在其构造函数中需要一个指向片段管理的指针,并且片段管理能从任意托管内存段通过成员函数get_segment_manager获得:
managed_shared_memory::segment_manager *seg_manager =
managed_shm.get_segment_manager();
获取构建对象的信息
一旦使用construct<>函数族构建了一个对象,程序员可以通过指向此对象的指针来获取对象信息。程序员能获得如下信息:
- 对象名:如果是一个具名实例,将返回构造函数中使用的名称,否则返回0。
- 对象长度:返回此对象的元素个数(如果是单个值,返回1;如果是数组则>=1)。
- 构建类型:是否此对象是具名、唯一或是匿名构建。
以下是功能示例:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <cassert>
#include <cstring>
class my_class
{
//...
};
int main()
{
using namespace boost::interprocess;
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;
managed_shared_memory managed_shm(create_only, "MySharedMemory", 10000*sizeof(std::size_t));
//Construct objects
my_class *named_object = managed_shm.construct<my_class>("Object name")[1]();
my_class *unique_object = managed_shm.construct<my_class>(unique_instance)[2]();
my_class *anon_object = managed_shm.construct<my_class>(anonymous_instance)[3]();
//Now test "get_instance_name" function.
assert(0 == std::strcmp(managed_shared_memory::get_instance_name(named_object), "Object name"));
assert(0 == managed_shared_memory::get_instance_name(unique_object));
assert(0 == managed_shared_memory::get_instance_name(anon_object));
//Now test "get_instance_type" function.
assert(named_type == managed_shared_memory::get_instance_type(named_object));
assert(unique_type == managed_shared_memory::get_instance_type(unique_object));
assert(anonymous_type == managed_shared_memory::get_instance_type(anon_object));
//Now test "get_instance_length" function.
assert(1 == managed_shared_memory::get_instance_length(named_object));
assert(2 == managed_shared_memory::get_instance_length(unique_object));
assert(3 == managed_shared_memory::get_instance_length(anon_object));
managed_shm.destroy_ptr(named_object);
managed_shm.destroy_ptr(unique_object);
managed_shm.destroy_ptr(anon_object);
return 0;
}
原子执行对象函数
有时程序员必须执行一些代码,且需要在执行代码时保证没有其他进程或线程会创建或销毁任何具名、唯一或匿名对象。用户可能想创建一些具名对象并初始化它们,但是这些对象能马上被其他进程可用。
为达此目的,程序员可以使用托管类提供的atomic_func()函数:
//This object function will create several named objects
create_several_objects_func func(/**/);
//While executing the function, no other process will be
//able to create or destroy objects
managed_memory.atomic_func(func);
注意到 atomic_func并不阻止其他进程分配原始内存或为已构建的对象执行成员函数(例如:另一个进程可能推元素至置于片段中的向量里)。此原子函数仅阻塞来自于其他进程的具名、唯一和匿名创建、查找和析构(并发调用construct<>, find<>, find_or_construct<>, destroy<>等等)。
托管内存片段的高级特性
获得关于托管片段的信息 增长的托管片段 高级索引函数 分配对齐的内存部分 多种分配函数 扩展内存分配 用写拷贝或只读模式打开托管共享内存或内存映射文件 |
获得关于托管片段的信息
以下这些函数能获得关于托管内存段的信息:
获得内存片段大小:
managed_shm.get_size();
获得内存片段的剩余字节:
managed_shm.get_free_memory();
将内存清空至0:
managed_shm.zero_free_memory();
了解是否所有内存被释放了,如果没有,返回false:
managed_shm.all_memory_deallocated();
测试托管片段的内部结构。如果未检测到错误,返回true:
managed_shm.check_sanity();
获得分配在片段中的具名和唯一对象的数目:
managed_shm.get_num_named_objects();
managed_shm.get_num_unique_objects();
增长的托管片段
一旦托管片段被创建,它就不能增长了。此限制不太容易解决:每个关联此托管片段的进程必须停止,被通知新的大小,然后它们需要重新映射托管片段再继续运行。没有系统内核的帮助,仅依靠用户级库几乎无法完成这些操作。
另一方面,Boost.Interprocess提供了线下片段增长。这意味着什么?也就是如果没有其他进程映射托管片段,此片段能被增长。如果应用程序能够找到没有进程关联片段的时刻,它能增长或缩减以适应托管片段。
这里我们有个例子演示如何增长和缩减以适应managed_shared_memory:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <cassert>
class MyClass
{
//...
};
int main()
{
using namespace boost::interprocess;
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;
{
//Create a managed shared memory
managed_shared_memory shm(create_only, "MySharedMemory", 1000);
//Check size
assert(shm.get_size() == 1000);
//Construct a named object
MyClass *myclass = shm.construct<MyClass>("MyClass")();
//The managed segment is unmapped here
}
{
//Now that the segment is not mapped grow it adding extra 500 bytes
managed_shared_memory::grow("MySharedMemory", 500);
//Map it again
managed_shared_memory shm(open_only, "MySharedMemory");
//Check size
assert(shm.get_size() == 1500);
//Check "MyClass" is still there
MyClass *myclass = shm.find<MyClass>("MyClass").first;
assert(myclass != 0);
//The managed segment is unmapped here
}
{
//Now minimize the size of the segment
managed_shared_memory::shrink_to_fit("MySharedMemory");
//Map it again
managed_shared_memory shm(open_only, "MySharedMemory");
//Check size
assert(shm.get_size() < 1000);
//Check "MyClass" is still there
MyClass *myclass = shm.find<MyClass>("MyClass").first;
assert(myclass != 0);
//The managed segment is unmapped here
}
return 0;
}
managed_mapped_file 也提供了类似的函数用于增长或缩减以适应托管文件。请记住在增长/缩减被执行时,任何进程都不能修改文件/共享内存。否则,托管片段将被损坏。
高级索引函数
正如提到的,托管片段存储具名和唯一对象的信息至两个索引中。依赖于这些索引的类型,当新的具名或唯一分配器被创建时,此索引必须重分配一些辅助结构。对一些索引来说,如果用户知道将创建多少具名或唯一对象时,可以提前分配一些结构来获得更好的性能。(如果索引是一个有序向量,则可以提前分配内存避免重分配。如果索引是一个哈希结构,则可以提前分配桶数组)。
以下函数保留了内存,以使对具名或唯一对象的后续分配更有效。这些函数仅对伪浸入或非节点索引(例如flat_map_index,iunorderd_set_index)有效。这些函数对默认索引(iset_index)或其他索引(map_index)无效:
managed_shm.reserve_named_objects(1000);
managed_shm.reserve_unique_objects(1000);
managed_shm.reserve_named_objects(1000);
managed_shm.reserve_unique_objects(1000);
托管内存片段也提供了通过已创建的具名和唯一对象进行迭代调试的可能性。小心:此类迭代不是线程安全的,因此用户必须保证没有其他线程正在片段中使用(创建、删除、预留)
具名或唯一索引。其他不牵涉到索引的操作可以并发执行(例如,原始内存分配/释放)。
以下函数返回表示具名和唯一对象存储在托管片段范围的常量迭代器。依赖于索引类型,当一个具名或唯一创建/删除/预留操作进行后,迭代器可能会失效:
typedef managed_shared_memory::const_named_iterator const_named_it;
const_named_it named_beg = managed_shm.named_begin();
const_named_it named_end = managed_shm.named_end();
typedef managed_shared_memory::const_unique_iterator const_unique_it;
const_unique_it unique_beg = managed_shm.unique_begin();
const_unique_it unique_end = managed_shm.unique_end();
for(; named_beg != named_end; ++named_beg){
//A pointer to the name of the named object
const managed_shared_memory::char_type *name = named_beg->name();
//The length of the name
std::size_t name_len = named_beg->name_length();
//A constant void pointer to the named object
const void *value = named_beg->value();
}
for(; unique_beg != unique_end; ++unique_beg){
//The typeid(T).name() of the unique object
const char *typeid_name = unique_beg->name();
//The length of the name
std::size_t name_len = unique_beg->name_length();
//A constant void pointer to the unique object
const void *value = unique_beg->value();
}
分配对齐的内存部分
有时,由于软硬件的限制,能够分配对齐的内存片段是很有趣的。有时候,拥有对齐的内存是一项可以用来提升某些内存算法的特性。
这种分配与之前展示的原始内存分配类似,但它需要一个额外的参数来指定对齐。关于对齐有一个限制:它必须是2的幂次方。
如果一个用户想分配许多对齐的内存块(例如对齐到128字节),则内存浪费的最小值是一个该值的倍数(例如2*128 - 一些字节)。导致此的原因是每种内存分配器通常需要一些额外的元数据在此被分配的缓冲区的头一批字节上。如果用户知道”一些字节”的值并且如果空闲内存块的头几个字节被用来填充对齐的分配,内存块的剩余部分也可以对齐并且为下次对齐分配做好准备。注意到请求一个对齐值整数倍的值并不是最优的,因为由于所需的元数据导致余下的内存块不是对齐的。
一旦编程者了解每次内存分配的有效载荷数,他能申请一个最优值,此值能达到申请值和未来对齐分配值的最大化组合。
下面是一个小例子,展示了如何使用对齐分配:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <cassert>
int main()
{
using namespace boost::interprocess;
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;
//Managed memory segment that allocates portions of a shared memory
//segment with the default management algorithm
managed_shared_memory managed_shm(create_only, "MySharedMemory", 65536);
const std::size_t Alignment = 128;
//Allocate 100 bytes aligned to Alignment from segment, throwing version
void *ptr = managed_shm.allocate_aligned(100, Alignment);
//Check alignment
assert((static_cast<char*>(ptr)-static_cast<char*>(0)) % Alignment == 0);
//Deallocate it
managed_shm.deallocate(ptr);
//Non throwing version
ptr = managed_shm.allocate_aligned(100, Alignment, std::nothrow);
//Check alignment
assert((static_cast<char*>(ptr)-static_cast<char*>(0)) % Alignment == 0);
//Deallocate it
managed_shm.deallocate(ptr);
//If we want to efficiently allocate aligned blocks of memory
//use managed_shared_memory::PayloadPerAllocation value
assert(Alignment > managed_shared_memory::PayloadPerAllocation);
//This allocation will maximize the size of the aligned memory
//and will increase the possibility of finding more aligned memory
ptr = managed_shm.allocate_aligned
(3*Alignment - managed_shared_memory::PayloadPerAllocation, Alignment);
//Check alignment
assert((static_cast<char*>(ptr)-static_cast<char*>(0)) % Alignment == 0);
//Deallocate it
managed_shm.deallocate(ptr);
return 0;
}
多种分配函数
如果一个应用程序需要分配许多内存缓冲区,但同时必须独立的释放它们,应用程序一般*循环调用allocate()。托管内存片段提供了一个迭代函数用于在单个获取内存缓冲区的调用中打包数个分配器:
- 在内存中连续打包(提高定域性)。
- 能被独立释放。
这种分配方法比循环调用allocate()要快许多。缺点是内存片段必须提供一块足够大的连续内存段以放置所有的分配器。
托管内存片段通过allocate_many()函数族提供了这些功能。下面是2种allocate_many函数:
- 分配N个相同大小的内存缓冲区。
- 分配N个不同大小的内存缓冲区。
//!Allocates n_elements of elem_size bytes.
multiallocation_iterator allocate_many(std::size_t elem_size, std::size_t min_elements, std::size_t preferred_elements, std::size_t &received_elements);
//!Allocates n_elements, each one of elem_sizes[i] bytes.
multiallocation_iterator allocate_many(const std::size_t *elem_sizes, std::size_t n_elements);
//!Allocates n_elements of elem_size bytes. No throwing version.
multiallocation_iterator allocate_many(std::size_t elem_size, std::size_t min_elements, std::size_t preferred_elements, std::size_t &received_elements, std::nothrow_t nothrow);
//!Allocates n_elements, each one of elem_sizes[i] bytes. No throwing version.
multiallocation_iterator allocate_many(const std::size_t *elem_sizes, std::size_t n_elements, std::nothrow_t nothrow);
所有函数返回一个多分配迭代器,它能被用于获得用户可覆盖的内存的指针。一个多分配迭代器:
- 变得无效如果其指向的内存被释放了或下一个迭代器(原本使用++运算符能获得的)变无效了。
- 从allocate_many返回的值能使用一个bool表达式检查,以便了解分配是否成功。
- 一个缺省的多分配迭代器构造预示着一个独立迭代器和“结束”迭代器。
- 解引用一个迭代器(运算符*())将返回一个char&引用了用户能够覆盖的内存缓冲区的第一个字节。
- 迭代器类别依赖于内存分配算法,但它至少是一个向前迭代器。
以下是一个小例子,展示了所有这些功能:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/detail/move.hpp> //boost::move
#include <cassert>//assert
#include <cstring>//std::memset
#include <new> //std::nothrow
#include <vector> //std::vector
int main()
{
using namespace boost::interprocess;
typedef managed_shared_memory::multiallocation_chain multiallocation_chain;
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;
managed_shared_memory managed_shm(create_only,"MySharedMemory", 65536);
//Allocate 16 elements of 100 bytes in a single call. Non-throwing version.
multiallocation_chain chain(managed_shm.allocate_many(100, 16, std::nothrow));
//Check if the memory allocation was successful
if(chain.empty()) return 1;
//Allocated buffers
std::vector<void*> allocated_buffers;
//Initialize our data
while(!chain.empty()){
void *buf = chain.front();
chain.pop_front();
allocated_buffers.push_back(buf);
//The iterator must be incremented before overwriting memory
//because otherwise, the iterator is invalidated.
std::memset(buf, 0, 100);
}
//Now deallocate
while(!allocated_buffers.empty()){
managed_shm.deallocate(allocated_buffers.back());
allocated_buffers.pop_back();
}
//Allocate 10 buffers of different sizes in a single call. Throwing version
managed_shared_memory::size_type sizes[10];
for(std::size_t i = 0; i < 10; ++i)
sizes[i] = i*3;
chain = managed_shm.allocate_many(sizes, 10);
managed_shm.deallocate_many(boost::move(chain));
return 0;
}
分配N个相同大小的缓冲区能提升池子和节点容器(例如类似于STL的链表)的性能:当插入一系列向前指针至类STL链表中,插入函数能够检测需要的元素数目并且分配一个单独调用。节点也能被释放。
分配N个不同大小的缓冲区能用于加速以下情况的分配:当数个对象必须总是在同一时间分配但在不同时间释放。例如,一个类可能执行数个初始化分配(例如一些网络包得头数据)在其构造函数中,同时也分配一块将来可能会被释放的缓冲区(通过网络传送的数据)。构造函数可能使用allocate_many()来加速初始化,而不是独立分配所有数据,但它也能释放并且增加可变大小元素的内存。
一般而言,allocate_many()当N很大时非常有用。过度使用allocate_many()会降低(译注:原E文此处为提高(increase))有效内存使用率,因为它不能重用已存在的不连续内存碎片,而这些碎片原本可以被一些元素使用。
扩展内存分配
当编程一些数据结构例如向量,内存重分配变为提高性能的重要工具。托管内存片段提供了一个高级的重分配函数,它能提供:
- 向前扩展:一个已分配的缓冲区能够被扩展以便缓冲区的尾部能进一步移动。新数据能被写入至旧尾部和新尾部之间。
- 向后扩展:一个已分配的缓冲区能够被扩展以便缓冲区的头能进向后移动。新数据能被写入至新头部和旧头部之间。
- 收缩:一个已分配的缓冲区能够收缩以便缓冲区的尾部能向后移动。位于新尾部和旧尾部间的内存能够被用于进一步分配。
扩展能与新缓冲区的分配结合使用,如果扩展没有获得一个带“扩展,如果分配失败一个新缓冲区”语义的函数。
除了这个功能,此函数总返回分配缓冲区的实际大小,因为很多时候,基于对齐问题,分配的缓冲区会比实际申请的大一些。所以,程序员可以使用分配命令来增大内存。
以下是函数声明:
enum boost::interprocess::allocation_type
{
//Bitwise OR (|) combinable values
boost::interprocess::allocate_new = ...,
boost::interprocess::expand_fwd = ...,
boost::interprocess::expand_bwd = ...,
boost::interprocess::shrink_in_place = ...,
boost::interprocess::nothrow_allocation = ...
};
template<class T>
std::pair<T *, bool>
allocation_command( boost::interprocess::allocation_type command
, std::size_t limit_size
, std::size_t preferred_size
, std::size_t &received_size
, T *reuse_ptr = 0);
此函数的前提条件:
- 如果参数命令包含了boost::interprocess::shrink_in_place,它就不能包含任何的these values: boost::interprocess::expand_fwd,boost::interprocess::expand_bwd。
- 如果参数命令包含了boost::interprocess::expand_fwd或 boost::interprocess::expand_bwd,则参数reuse_ptr必须为非空并且由之前的分配函数返回。
- 如果参数命令包含了boost::interprocess::shrink_in_place,则参数limit_size必须等于或大于参数preferred_size。
- 如果参数命令包含了任何的boost::interprocess::expand_fwd或boost::interprocess::expand_bwd,则参数limit_size必须等于或小于参数preferred_size。
此函数的效果:
- 如果参数命令包含了boost::interprocess::shrink_in_place,函数将尝试仅移动内存块的尾部而达到减少内存块的大小至被指针reuse_ptr指向值preferred_size值的目的。如果不可能,它将尝试尽可能多的减少内存块的大小,只要size(p) <= limit_size。只要preferred_size <= size(p)且size(p) <= limit_size,则操作成功。
- 如果参数命令仅包含了boost::interprocess::expand_fwd(带额外的可选参数additional boost::interprocess::nothrow_allocation),分配器将尝试通过仅移动内存块的尾部,从而增加reuse指针指向的内存块的大小至preferred_size值。如果不可能,它将尝试尽可能多的增加内存块大小,只要满足size(p) >= limit_size。只有当limit_size <=size(p)时,操作成功。
- 如果参数命令仅包含了boost::interprocess::expand_bwd(带额外的可选参数boost::interprocess::nothrow_allocation),分配器将尝试通过仅移动内存块的头部,从而增加reuse_ptr指针指向的内存块的大小至返回的new_ptr新地址。如果不可能,它将尝试尽可能多的移动内存块的头部,只要满足size(new_ptr) >= limit_size。只有当limit_size <= size(new_ptr)时,操作成功。
- 如果参数命令仅包含了boost::interprocess::allocate_new(带额外的可选参数boost::interprocess::nothrow_allocation),分配器将尝试为preferred_size对象分配内存。如果不可能,它将尝试为至少limit_size的对象分配内存。
- 如果参数命令仅包含了boost::interprocess::expand_fwd和boost::interprocess::allocate_new(带额外的可选参数boost::interprocess::nothrow_allocation),分配器首先尝试向前扩展。如果失败,它将尝试一个新的分配。
- 如果参数命令仅包含了boost::interprocess::expand_bwd和boost::interprocess::allocate_new(带额外的可选参数boost::interprocess::nothrow_allocation),分配器首先尝试如果需要使用两种方式获得preferred_size对象。如果失败,将尝试如果需要使用两种方式获得limit_size对象。
- 如果参数命令仅包含了boost::interprocess::expand_fwd和boost::interprocess::expand_bwd(带额外的可选参数boost::interprocess::nothrow_allocation),分配器首先尝试向前扩展。如果失败,它将尝试使用向后扩展或结合向前向后扩展来获得 preferred_size对象。如果还失败,如果需要,它将使用两种方式获得limit_size对象。
- 如果参数命令仅包含了boost::interprocess::expand_fwd和boost::interprocess::expand_bwd(带额外的可选参数boost::interprocess::nothrow_allocation),分配器首先尝试向前扩展。如果失败,它将尝试使用新的分配方式,向后扩展或结合向前向后扩展来获得 preferred_size对象。如果还失败,它将使用相同的方法获得limit_size对象。
- 分配器总会将扩展的/分配的/收缩的内存块大小写入至received_size。一旦写失败了,可以采用limit_size参数再调用一次。
如果两个条件满足了,将抛异常:
- 分配器不能分配/扩展/收缩内存或前提条件有误。
- 参数命令没有包含boost::interprocess::nothrow_allocation。
此函数返回:
- 如果参数命令包含了boost::interprocess::nothrow_allocation,已分配内存的地址或扩展内存的新地址做为pair的第一个成员。如果分配/扩展失败了或前提条件有误,则第一个成员为0。
- 如果内存已经被分配了,pair的第二个成员为false;如果内存被扩展,则为true。如果第一个成员为0,则第二个成员是个未定义的值。
注意:
如果用户选择了char作为模板参数,则返回的缓冲区将会适当的对齐以容纳任何类型。
如果用户选择了char作为模板参数并且指定了向后扩展,尽管正确对齐了,但返回的缓冲区可能不适合,因为新旧开始点的距离可能不是用户构建结构大小的整数倍,这主要基于由于内部限制导致的扩展会比申请的字节大一些。当执行向后扩展时,如果你已经在旧的缓冲区上构建了对象,请确保指定了正确的类型。
下面是一个小例子,展示了如何使用allocation_command:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <cassert>
int main()
{
using namespace boost::interprocess;
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;
//Managed memory segment that allocates portions of a shared memory
//segment with the default management algorithm
managed_shared_memory managed_shm(create_only, "MySharedMemory", 10000*sizeof(std::size_t));
//Allocate at least 100 bytes, 1000 bytes if possible
managed_shared_memory::size_type min_size = 100, preferred_size = 1000;
managed_shared_memory::size_type received_size;
std::size_t *ptr = managed_shm.allocation_command<std::size_t>
(boost::interprocess::allocate_new, min_size, preferred_size, received_size).first;
//Received size must be bigger than min_size
assert(received_size >= min_size);
//Get free memory
managed_shared_memory::size_type free_memory_after_allocation = managed_shm.get_free_memory();
//Now write the data
for(std::size_t i = 0; i < received_size; ++i) ptr[i] = i;
//Now try to triplicate the buffer. We won't admit an expansion
//lower to the double of the original buffer.
//This "should" be successful since no other class is allocating
//memory from the segment
managed_shared_memory::size_type expanded_size;
std::pair<std::size_t *, bool> ret = managed_shm.allocation_command
(boost::interprocess::expand_fwd, received_size*2, received_size*3, expanded_size, ptr);
//Check invariants
assert(ret.second == true);
assert(ret.first == ptr);
assert(expanded_size >= received_size*2);
//Get free memory and compare
managed_shared_memory::size_type free_memory_after_expansion = managed_shm.get_free_memory();
assert(free_memory_after_expansion < free_memory_after_allocation);
//Write new values
for(std::size_t i = received_size; i < expanded_size; ++i) ptr[i] = i;
//Try to shrink approximately to min_size, but the new size
//should be smaller than min_size*2.
//This "should" be successful since no other class is allocating
//memory from the segment
managed_shared_memory::size_type shrunk_size;
ret = managed_shm.allocation_command
(boost::interprocess::shrink_in_place, min_size*2, min_size, shrunk_size, ptr);
//Check invariants
assert(ret.second == true);
assert(ret.first == ptr);
assert(shrunk_size <= min_size*2);
assert(shrunk_size >= min_size);
//Get free memory and compare
managed_shared_memory::size_type free_memory_after_shrinking = managed_shm.get_free_memory();
assert(free_memory_after_shrinking > free_memory_after_expansion);
//Deallocate the buffer
managed_shm.deallocate(ptr);
return 0;
}
allocation_command是非常强大的函数,它能引起重要的性能提升。在编写类似于vector的数据结构时尤其有用,在此情况下,程序员能够使分配内存请求数目和内存浪费都达到最小。
用写拷贝或只读模式打开托管共享内存或内存映射文件
当映射一个基于共享内存或文件的内存片段时,可以选择open_copy_on_write选项来打开它们。此选项类似与open_only但是每一个程序员对此托管片段的改变都仅对此进程私有,并且不会传导至底层设备(共享内存或文件)。
底层的共享内存或文件被read_only打开,因此多个进程可以共享一个初始内存片段并且使修改对其私有。如果很多进程以写拷贝并且不修改的方式打开一个托管片段,则托管片段的页面将在所有进程间共享,这节省了很多内存。
用open_read_only模式打开托管共享内存或映射文件,将以read-only属性映射底层设备至内存中。这意味着任何尝试写此内存的操作,不管是创建对象或是锁互斥量都会导致操作系统页面缺陷错误(然后,程序中止)。Read-only模式以只读模式打开底层设备(共享内存、文件等)并且如果多个进程仅想处理一个托管内存片段而不修改它时,这将节省可观的内存消耗。Read-only模式操作限制如下:
- Read-only模式仅能使用于托管类。如果程序员获得了片段管理并且尝试直接使用它,则可能导致禁止访问。其原因为片段管理器是位于底层设备中的,并且与其映射在内存中的模式没有任何关系。
- 只有托管片段的常量成员函数能使用。
- 此外,find<>成员函数应避免使用内部锁,它能被用于查找具名和唯一对象。
以下是例子,它展示了这两种打开模式的使用:
#include <boost/interprocess/managed_mapped_file.hpp>
#include <fstream> //std::fstream
#include <iterator>//std::distance
int main()
{
using namespace boost::interprocess;
//Define file names
const char *ManagedFile = "MyManagedFile";
const char *ManagedFile2 = "MyManagedFile2";
//Try to erase any previous managed segment with the same name
file_mapping::remove(ManagedFile);
file_mapping::remove(ManagedFile2);
remove_file_on_destroy destroyer1(ManagedFile);
remove_file_on_destroy destroyer2(ManagedFile2);
{
//Create an named integer in a managed mapped file
managed_mapped_file managed_file(create_only, ManagedFile, 65536);
managed_file.construct<int>("MyInt")(0u);
//Now create a copy on write version
managed_mapped_file managed_file_cow(open_copy_on_write, ManagedFile);
//Erase the int and create a new one
if(!managed_file_cow.destroy<int>("MyInt"))
throw int(0);
managed_file_cow.construct<int>("MyInt2");
//Check changes
if(managed_file_cow.find<int>("MyInt").first && !managed_file_cow.find<int>("MyInt2").first)
throw int(0);
//Check the original is intact
if(!managed_file.find<int>("MyInt").first && managed_file.find<int>("MyInt2").first)
throw int(0);
{ //Dump the modified copy on write segment to a file
std::fstream file(ManagedFile2, std::ios_base::out | std::ios_base::binary);
if(!file)
throw int(0);
file.write(static_cast<const char *>(managed_file_cow.get_address()), (std::streamsize)managed_file_cow.get_size());
}
//Now open the modified file and test changes
managed_mapped_file managed_file_cow2(open_only, ManagedFile2);
if(managed_file_cow2.find<int>("MyInt").first && !managed_file_cow2.find<int>("MyInt2").first)
throw int(0);
}
{
//Now create a read-only version
managed_mapped_file managed_file_ro(open_read_only, ManagedFile);
//Check the original is intact
if(!managed_file_ro.find<int>("MyInt").first && managed_file_ro.find<int>("MyInt2").first)
throw int(0);
//Check the number of named objects using the iterators
if(std::distance(managed_file_ro.named_begin(), managed_file_ro.named_end()) != 1 &&
std::distance(managed_file_ro.unique_begin(), managed_file_ro.unique_end()) != 0 )
throw int(0);
}
return 0;
}
托管堆内存和托管外部缓冲区
托管外部缓冲区:构建所有的Boost.Interprocess对象在用户提供的缓冲区中 托管堆内存:Boost.Interprocess对内存机制 与托管内存片段的不同之处 例子:通过消息队列序列化一个数据库 |
Boost.Interprocess使用managed_shared_memory或managed_mapped_file为进程间提供了托管共享内存。两个进程仅需映射相同的可映射内存资源,然后在此对象上读写。
很多时候,我们不想使用这种共享内存方式,我们更希望通过网络、本地socket或消息队列发送序列化的数据。序列化过程可以使用 Boost.Serialization或类似的库来完成。但是,如果两个进程共享相同的ABI(应用程序二进制接口),我们能够使用同样的 managed_shared_memory或managed_heap_memory 的对象和容器构建能力来在一个单独的缓冲区中创建所有信息,例如它将采用消息队列发送。接受者仅拷贝数据至本地缓冲区,它能够直接读取或修改而不需要反序列化数据。这种方式比复杂的序列化机制要有效得多。
Boost.Interprocess服务中的应用程序使用非共享内存缓冲区:
- 在动态内存不值得推荐系统中,创建和使用STL兼容的容器和分配器。
- 构建复杂的、容易序列化的数据库在一个单个缓冲区中:
- 用于线程间共享数据
- 用于保存和加载信息从/至文件中。
- 复制信息(容器、分配器等),仅拷贝内容从一个缓冲区至另一个。
- 使用序列化/进程间/网络通信发送复杂的信息和对象/数据库。
为协助这种管理,Boost.Interprocess提供了两个有用的类,basic_managed_heap_memory和basic_managed_external_buffer:
托管外部缓冲区:构建所有的Boost.Interprocess对象在用户提供的缓冲区中
有时,用户想创建简单对象、STL兼容容器、STL兼容字符串以及更多在一个单个的缓冲区中。此缓冲区可以使一个大的静态缓冲区、一个内存映射辅助设备或任何其他的用户缓冲区。
这样就允许一种简单的序列化并且我们将仅须拷贝缓冲区来赋值所有创建在原始缓冲区中的对象,包括复杂对象例如映射表、链表等。Boost.Interprocess提供托管内存片段类来处理用户提供的缓冲区,它具有与共享内存类相同的功能。
//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
template <
class CharType,
class MemoryAlgorithm,
template<class IndexConfig> class IndexType
>
class basic_managed_external_buffer;
//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
// Names are c-strings,
// Default memory management algorithm
// (rbtree_best_fit with no mutexes and relative pointers)
// Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_external_buffer <
char,
rbtree_best_fit<null_mutex_family, offset_ptr<void> >,
flat_map_index
> managed_external_buffer;
//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
// Names are wide-strings,
// Default memory management algorithm
// (rbtree_best_fit with no mutexes and relative pointers)
// Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_external_buffer<
wchar_t,
rbtree_best_fit<null_mutex_family, offset_ptr<void> >,
flat_map_index
> wmanaged_external_buffer;
要使用托管外部缓冲区,你必须包含如下头文件:
#include <boost/interprocess/managed_external_buffer.hpp>
让我们看看一个使用managed_external_buffer的例子:
#include <boost/interprocess/managed_external_buffer.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/list.hpp>
#include <cstring>
#include <boost/aligned_storage.hpp>
int main()
{
using namespace boost::interprocess;
//Create the static memory who will store all objects
const int memsize = 65536;
static boost::aligned_storage<memsize>::type static_buffer;
//This managed memory will construct objects associated with
//a wide string in the static buffer
wmanaged_external_buffer objects_in_static_memory
(create_only, &static_buffer, memsize);
//We optimize resources to create 100 named objects in the static buffer
objects_in_static_memory.reserve_named_objects(100);
//Alias an integer node allocator type
//This allocator will allocate memory inside the static buffer
typedef allocator<int, wmanaged_external_buffer::segment_manager>
allocator_t;
//Alias a STL compatible list to be constructed in the static buffer
typedef list<int, allocator_t> MyBufferList;
//The list must be initialized with the allocator
//All objects created with objects_in_static_memory will
//be stored in the static_buffer!
MyBufferList *list = objects_in_static_memory.construct<MyBufferList>(L"MyList")
(objects_in_static_memory.get_segment_manager());
//Since the allocation algorithm from wmanaged_external_buffer uses relative
//pointers and all the pointers constructed int the static memory point
//to objects in the same segment, we can create another static buffer
//from the first one and duplicate all the data.
static boost::aligned_storage<memsize>::type static_buffer2;
std::memcpy(&static_buffer2, &static_buffer, memsize);
//Now open the duplicated managed memory passing the memory as argument
wmanaged_external_buffer objects_in_static_memory2
(open_only, &static_buffer2, memsize);
//Check that "MyList" has been duplicated in the second buffer
if(!objects_in_static_memory2.find<MyBufferList>(L"MyList").first)
return 1;
//Destroy the lists from the static buffers
objects_in_static_memory.destroy<MyBufferList>(L"MyList");
objects_in_static_memory2.destroy<MyBufferList>(L"MyList");
return 0;
}
Boost.Interprocess的STL兼容分配器也能被用于放置STL兼容容器在用户内存片段中。
basic_managed_external_buffer在嵌入式系统中构建小型数据库时也是非常有用的,它能限制使用的内存大小至预定义的内存块,防止数据库分割堆内存。
托管堆内存:Boost.Interprocess对内存机制
使用堆内存(new/delete)来获得用于用户存储所有其数据的缓冲区是非常普遍的,因此Boost.Interprocess提供了一些特定的类,它们专门与堆内存一起工作。
以下是这些类:
//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
template <
class CharType,
class MemoryAlgorithm,
template<class IndexConfig> class IndexType
>
class basic_managed_heap_memory;
//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
// Names are c-strings,
// Default memory management algorithm
// (rbtree_best_fit with no mutexes and relative pointers)
// Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_heap_memory <
char,
rbtree_best_fit<null_mutex_family>,
flat_map_index
> managed_heap_memory;
//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
// Names are wide-strings,
// Default memory management algorithm
// (rbtree_best_fit with no mutexes and relative pointers)
// Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_heap_memory<
wchar_t,
rbtree_best_fit<null_mutex_family>,
flat_map_index
> wmanaged_heap_memory;
为使用托管堆内存,你必须包含如下头文件:
#include <boost/interprocess/managed_heap_memory.hpp>
使用方法与basic_managed_external_buffer完全相同,除了托管内存片段使用动态的(new/delete)方法创建内存之外。
basic_managed_heap_memory也提供了一个增长(std::size_t extra_bytes)函数,它尝试重分配内部堆内存以便我们具有容纳更多对象的空间。但是小心,如果内存重分配,旧的缓冲区将被拷贝至新的缓冲区,因此所有的对象对被二进制拷贝至新的缓冲区。为了使用此函数,所有创建在堆内存中用于指向堆内存中对象的指针必须为相对指针(例如,offset_ptr)。否则,结果未定义。下面是例子:
#include <boost/interprocess/containers/list.hpp>
#include <boost/interprocess/managed_heap_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <cstddef>
using namespace boost::interprocess;
typedef list<int, allocator<int, managed_heap_memory::segment_manager> >
MyList;
int main ()
{
//We will create a buffer of 1000 bytes to store a list
managed_heap_memory heap_memory(1000);
MyList * mylist = heap_memory.construct<MyList>("MyList")
(heap_memory.get_segment_manager());
//Obtain handle, that identifies the list in the buffer
managed_heap_memory::handle_t list_handle = heap_memory.get_handle_from_address(mylist);
//Fill list until there is no more memory in the buffer
try{
while(1) {
mylist->insert(mylist->begin(), 0);
}
}
catch(const bad_alloc &){
//memory is full
}
//Let's obtain the size of the list
MyList::size_type old_size = mylist->size();
//To make the list bigger, let's increase the heap buffer
//in 1000 bytes more.
heap_memory.grow(1000);
//If memory has been reallocated, the old pointer is invalid, so
//use previously obtained handle to find the new pointer.
mylist = static_cast<MyList *>
(heap_memory.get_address_from_handle(list_handle));
//Fill list until there is no more memory in the buffer
try{
while(1) {
mylist->insert(mylist->begin(), 0);
}
}
catch(const bad_alloc &){
//memory is full
}
//Let's obtain the new size of the list
MyList::size_type new_size = mylist->size();
assert(new_size > old_size);
//Destroy list
heap_memory.destroy_ptr(mylist);
return 0;
}
与托管内存片段的不同之处
所有托管内存片段具有相似的能力(内存片段内的内存分配、具名对象构建),但在managed_shared_memory, managed_mapped_file和managed_heap_memory, managed_external_file之间还是有一些显著的区别。
- 托管共享内存和映射文件的缺省特例使用进程间共享互斥量。堆内存和外部缓冲区没有缺省的内部同步机制。原因是前两者被认为用于进程间共享(尽管内存映射文件能被仅用来使一个进程获得持久的对象数据库)而后两者被认为用于一个进程内来构建有序的具名对象数据库,它们通过一些列的进程间通信(例如消息队列、本地网络)发送。
- 前两个将创建一个被数个进程共享的系统全局对象(一个共享内存对象或一个文件),然而后两者不创建系统范围内的资源。
例子:通过消息队列序列化一个数据库
为看到托管堆内存和托管外部缓冲区类的效用,以下的例子展示了如何使用一个消息队列来序列化一整个采用Boost.Interprocess构建于内存缓冲区中的数据库,通过消息队列发送此数据库并且在另一个缓冲区中复制它。
//This test creates a in memory data-base using Interprocess machinery and
//serializes it through a message queue. Then rebuilds the data-base in
//another buffer and checks it against the original data-base
bool test_serialize_db()
{
//Typedef data to create a Interprocess map
typedef std::pair<const std::size_t, std::size_t> MyPair;
typedef std::less<std::size_t> MyLess;
typedef node_allocator<MyPair, managed_external_buffer::segment_manager>
node_allocator_t;
typedef map<std::size_t,
std::size_t,
std::less<std::size_t>,
node_allocator_t>
MyMap;
//Some constants
const std::size_t BufferSize = 65536;
const std::size_t MaxMsgSize = 100;
//Allocate a memory buffer to hold the destiny database using vector<char>
std::vector<char> buffer_destiny(BufferSize, 0);
message_queue::remove(test::get_process_id_name());
{
//Create the message-queues
message_queue mq1(create_only, test::get_process_id_name(), 1, MaxMsgSize);
//Open previously created message-queue simulating other process
message_queue mq2(open_only, test::get_process_id_name());
//A managed heap memory to create the origin database
managed_heap_memory db_origin(buffer_destiny.size());
//Construct the map in the first buffer
MyMap *map1 = db_origin.construct<MyMap>("MyMap")
(MyLess(),
db_origin.get_segment_manager());
if(!map1)
return false;
//Fill map1 until is full
try{
std::size_t i = 0;
while(1){
(*map1)[i] = i;
++i;
}
}
catch(boost::interprocess::bad_alloc &){}
//Data control data sending through the message queue
std::size_t sent = 0;
message_queue::size_type recvd = 0;
message_queue::size_type total_recvd = 0;
unsigned int priority;
//Send whole first buffer through the mq1, read it
//through mq2 to the second buffer
while(1){
//Send a fragment of buffer1 through mq1
std::size_t bytes_to_send = MaxMsgSize < (db_origin.get_size() - sent) ?
MaxMsgSize : (db_origin.get_size() - sent);
mq1.send( &static_cast<char*>(db_origin.get_address())[sent]
, bytes_to_send
, 0);
sent += bytes_to_send;
//Receive the fragment through mq2 to buffer_destiny
mq2.receive( &buffer_destiny[total_recvd]
, BufferSize - recvd
, recvd
, priority);
total_recvd += recvd;
//Check if we have received all the buffer
if(total_recvd == BufferSize){
break;
}
}
//The buffer will contain a copy of the original database
//so let's interpret the buffer with managed_external_buffer
managed_external_buffer db_destiny(open_only, &buffer_destiny[0], BufferSize);
//Let's find the map
std::pair<MyMap *, managed_external_buffer::size_type> ret = db_destiny.find<MyMap>("MyMap");
MyMap *map2 = ret.first;
//Check if we have found it
if(!map2){
return false;
}
//Check if it is a single variable (not an array)
if(ret.second != 1){
return false;
}
//Now let's compare size
if(map1->size() != map2->size()){
return false;
}
//Now let's compare all db values
MyMap::size_type num_elements = map1->size();
for(std::size_t i = 0; i < num_elements; ++i){
if((*map1)[i] != (*map2)[i]){
return false;
}
}
//Destroy maps from db-s
db_origin.destroy_ptr(map1);
db_destiny.destroy_ptr(map2);
}
message_queue::remove(test::get_process_id_name());
return true;
}