Boost的提供了一套ipc的接口,内存映射文件将文件的内容映射到进程的地址空间。
1 |
原生的file_mapping接口提供了创建一个内存映射文件,然后通过mapped_region进行进程地址空间的映射,获取映射到进程空间的地址,并在此地址进行对象的构造和操作。
由于直接在映射的地址进行复杂数据结构的构造很复杂的事情,原因是:映射后只能获得一个起始地址,要在这个地址上进行对象的构建和销毁,都要基于这个地址,也就是所构建的对象内部必须都是相对地址,不能使用现有的stl的容器,因为他们都是构建在堆上的,内存使用的都是指针,不是一个偏移。
所以boost提供了更高层次的接口managed_mapped_file
1 |
managed_mapped_file允许在内存映射文件上直接构建复杂的boost的数据结构。是不是很兴奋。。。
Managed_mapped_file不能在map file上构建STL的容器。和原生的file_mapping一样,托管内存映射文件类只是隐藏了在映射空间上构建对象的细节,它本身依然是基于一个映射起始地址的字节操作。内部使用的都是相对偏移地址,所以不能使用STL容器。
在使用managed_mapped_file进行数据结构构造之前,要知道:
- Allocator:的含义和工作原理
- Managed_mapped_file的接口含义
在C++的stl中,allocator作为所有容器的标配,负责所有数据结构的动态内存的分配和销毁、对象的构造和析构。和new表达式的功能可以理解为一样的。那这里理一下C++的内存分配。
new表达式执行过程:
- 首先会调用operator new这个标准库函数,分配足够的原始未类型化的内存
- 然后调用该类型的构造函数进行对象的构造
- 最后返回对象的指针
new表达式执行过程中内存分配和对象的构造是无法分开的。但有时候我们希望内存的分配和对象的构造进行分离。因为new可能在某些类的创建过程中带来很多运行时的开销。具体原因有如下:
- 可能会创建从不使用的对象
- 使用预先分配对象的时候,被使用的对象必须重新赋值
- 若预分配的内存必须被构造,某些类就不能使用它。
基于上面的理由,就出现了动态内存的预分配技术,即allocator。allocator类中allocate()和deallocate()成员负责内存的分配,construct()和destroy()负责对象的构造和销毁。”
C++标准中的allocator类主要包含以下4个成员:
引用C++ primer中的一句话
现代C++程序一般应该使用allocator类来分配内存,它更安全更灵活。
至于allocator的实现,在SGI STL中,标准的allocator类内存使用operator new和operator delete进行内存的分配和释放。特殊的allocator(所有stl 容器默认使用的),采用的二级配置器,分配算法采用:**内存池 + free list**, 内存的分配采用c的malloc和free。这里就不细说了。
说到这里allocator的功能和用途也很清楚了。
那么现在看一下managed_mapped_file简单的类图关系:
当需要在一个托管内存段(这里不止使用于mapped file,也同样试用共享内存,堆内存)上构建一个具名对象,boost ipc提供了construct 和find_or_construct接口。
我们可以直接调用managed_mapped_file的这两个成员进行对象的构造,由上面的类图关系可知,对象的构造最终依赖与segment_manager
那么segment_manager是如何工作的呢?
managed_mapped_file的segment_manager等同于allocator,那么其工作原理其实就是简单的分为两个过程:
- 内存的分配和释放
- 对象的构造和销毁
managed_mapped_file对象在创建的时候,会初始化它本身的segment_manager.
1 | typedef typename segment_manager_type |
segment_manager字面意思为段管理器。其实它本质上也就是一个allocator空间配置器,它全权负责内存映射文件对应到的进程空间地址的分配和管理。这就是前面我啰嗦那么多stl allocator的概念的原因. Segment_manager的核心是MemoryAlgorithm,MemoryAlgorithm负责内存的分配和销毁,它的实现是在managed_mapped_file在创建的时候传入的,如下。
1 | typedef basic_managed_mapped_file |
默认的managed_mapped_file在创建的时候,采用的内存管理算法rbtree_best_fit对segment_manager进行初始化,这个算法很高级。。。通过rb对空闲内存块排序,时间复杂度在lgn。这里先不深揪了,和free list算法比,由于没有看rbtree_best_fit的实现,表象上来看free list的时间复杂度是O(1),至于内存碎片什么的,应该不如rbtree_best_fit。
所以说segment_manager是managed_mapped_file的核心。
这里就不画segment_manager的类图了,简单的截了si的成员列表:
segment_manager实现了allocator类最基本的四个成员:allocate()&deallocate(), construct()&destroy()。用于内存的分配和释放,对象的构建和销毁。
好了,说到这里,上一段代码吧:
1 |
|
现在解释代码中的关键结构:
在managed_mappped_file上构建boost::interprocess容器时,容器的allocator模版参数一定要传入managed_mappped_file::segment_manager。这个原因就是本文的阐述:当对容器对象进行构建的时候,需要在mapped_file 的地址空间进行对象的构造,因为managed_mappped_file的内存分配和对象构造都是由segment_manager负责的,所以有示例代码:
1 | typedef bipc::node_allocator<float, mapped_segment_manager_t> vec_allocator_t; |
如果struct Msg为POD类型,Msg就不需要下面那个构造函数,但Msg中含有vector_t
.当Msg对象在构造的时候,需要递归调用成员vector_t score的构造函数进行score对象的构造,这个时候是肯定需要知道vector_t的allocator对象的,所以需要传入vec_allocator_t的对象。
1 | struct Msg |