文档章节

Boost IPC共享内存的使用总结

lday
 lday
发布于 2016/07/31 15:29
字数 2716
阅读 827
收藏 1

共享内存,是进程间数据传输的方式之一。数据发送方将数据放入共享内存中,数据接收方则从共享内存中将数据读出,进而完成整个数据的传输。在Linux平台下,操作系统为用户提供了两套共享内存的接口:

System-V共享内存接口
int shmget(…) 创建一块共享内存
int shmat(…) 附接一块共享内存
int shmdt(…) 断接一块共享内存
int shmctl(…) 获取共享内存对象信息,删除一块共享内存
Posix共享内存接口
int shm_open(…) 创建or打开共享内存对象
int ftruncate(…) 设置共享内存的大小
int fstat(…) 获取共享内存对象信息
Int shm_unlink(…) 删除共享内存
void *mmap(…) 将共享内存对象映射到本地进程空间中

在Windows平台下,操作系统为用户提供的共享内存接口为:

Windows平台共享内存接口
HANDLE CreateFileMapping(…) 创建一块共享内存
HANDLE OpenFileMapping(…) 附接一块共享内存
void *MapViewOfFile(…) 将共享内存映射到本地进程空间中
void UnmapViewOfFile(…) 释放共享内存
void CloseHandle(…) 关闭共享内存映射文件句柄

不同平台提供了不同的共享内存使用接口。C++ Boost库对两类平台的共享内存接口和使用方式提供了进一步抽象和封装,并将该部分功能放置在interprocess库中。完整的interprocess库介绍可参考:http://www.boost.org/doc/libs/1_57_0/doc/html/interprocess.html

这里我通过使用场景的方式简单总结boost::interprocess share_memory的使用

一、共享内存池的创建及销毁

在使用共享内存之前,我们都需要创建一块共享内存。直接使用操作系统的共享内存,则在不同的平台调用指定接口即可。Boost::ipc,则需要首先创建一个托管共享内存池,所有的共享内存对象,都基于该内存池来分配内存。
本文描述的使用场景主要依赖下述头文件:
 

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/allocators/allocator.hpp>

namespace bip = boost::interprocess;

共享内存池的创建:

static char *shareMemoryBlockName = "MySharedMemory";
static int32_t sharedMemoryBlockTotalSize = 65536;  

//创建共享内存
bip::managed_shared_memory segment(bip::create_only,
	shareMemoryBlockName,  //segment name
	sharedMemoryBlockTotalSize);

共享内存的附接:

//首先attach共享内存
//首先需要attach到share memory block
bip::managed_shared_memory segment(bip::open_only,
	shareMemoryBlockName  //segment name
	);

对应的,共享内存池名称,将共享内存池删除:

//同时销毁共享内存
bool isRemoved = bip::shared_memory_object::remove(shareMemoryBlockName);
if (isRemoved)
{
	cout << "Share Memory=[" << shareMemoryBlockName << "] removed!" << endl;
}
else
{
	cout << "Share Memory=[" << shareMemoryBlockName << "] not removed!" << endl;
}

【注】:使用时需确保segemnt对象整个生命周期的完整性,不能在segement已经析构后,还使用由该共享内存池创建的对象。

二、共享内存对象的创建及销毁

在共享内存池创建好之后,则可以基于共享内存池创建对象。在共享内存池上可创建有名/无名对象:

1. 有名对象的创建、附接、删除

有名对象的创建时,需要给该对象分配指定的对象名称

//bip::managed_shared_memory segment

//有名对象的创建
MyType *instance = segment.construct<MyType>  
         ("MyType instance")  //name of the object  
         (0.0, 0);            //ctor first argument 

创建有名共享内存对象的好处是,其他进程可以通过该对象名称,基于同一个共享内存池,方便的定位到该对象。对应的需要附接到该共享内存对象,则只需通过共享内存池找到指定命名对象:

//bip::managed_shared_memory segment

//有名对象的附接
typedef std::pair<MyType *, bip::managed_shared_memory::size_type> ResultT;
ResultT res = segment.find<MyType>("MyType instance");
if (!res.second)
{
	cout << "could not find MyType instance" << endl;
}

有名对象的删除,则通过调用destroy接口完成删除动作:

//bip::managed_shared_memory segment

//有名对象的删除
segment.destroy<MyType>("MyType instance");

2. 匿名对象的创建、使用及销毁

2.1 基于偏移指针的共享数据使用

大多数时候,我们其实不打算使用有名对象,而是使用无名对象,此时,我们需要使用共享内存池提供的匿名对象执行共享内存对象的创建:

//bip::managed_shared_memory segment

//匿名对象的创建
MyType *ptr = segment.construct<MyType>(bip::anonymous_instance) (par1, par2...);  

由于匿名对象无法通过名称指定定位,因此,当我们将一个匿名对象由进程A写入共享内存时,进程B没法直接拿到共享内存,进而使用。Boost::interprocess提供了方法来使得进程B能够获取匿名对象的内容。具体做法是:使用boost::interprocess::offset_ptr。我们在共享内存中创建匿名共享内存对象,通过offset_ptr指向共享内存对象。将offset_ptr由进程A发送到进程B,进程B再通过offset_ptr将实际内容读出来。由于offset_ptr指向的对象是存放在共享内存中,因此,对应的对象应当具备从共享内存中分配内存空间的能力,也就是说该对象的内存分配器应道指向对应共享内存的内存分配器。

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>

namespace bip = boost::interprocess;   //给ipc设置别名

//首先定义共享内存分配器类型
typedef bip::allocator<char, bip::managed_shared_memory::segment_manager> CharAllocator;
typedef bip::basic_string<char, std::char_traits<char>, CharAllocator> shared_string;

struct IPCData
{
	shared_string name;
	int       id;
	boost::atomic<int> atomicShrMmOrderRefCnt;

	IPCData(const shared_string::allocator_type& al):name(al),atomicShrMmOrderRefCnt(1)
	{
		id = 0;
	}

};

//定义数据的OffsetPtr
typedef bip::offset_ptr<IPCData> IPCDataOffsetPtrT;

//定义该offsetPtr的内存分配器
typedef bip::allocator<IPCDataOffsetPtrT, bip::managed_shared_memory::segment_manager>IPCDataOffsetPtrAllocatorT;

定义好对应的偏移地址指针后,就可以通过创建匿名共享内存对象的方式,将共享内存对象托管给偏移地址指针:

//bip::managed_shared_memory segment

//创建匿名共享内存offset_ptr指针
IPCDataOffsetPtrT ipcDataOffsetPtr =
	segment.construct<IPCData>(bip::anonymous_instance)
		(shared_string::allocator_type(segment.get_segment_manager()));

此后,定义相应的共享内存通道,让进程A将offset_ptr放入通道。

#include <boost/interprocess/containers/list.hpp>

namespace bip = boost::interprocess;   //给ipc设置别名

//定义基于该offsetPtr以及对应的内存分配器的queue
typedef bip::list<IPCDataOffsetPtrT, IPCDataOffsetPtrAllocatorT> IPCDataOffsetPtrQueueT;
//bip::managed_shared_memory segment
//ipcDataOffsetPtr 

//找到对应的allocator
const IPCDataOffsetPtrAllocatorT alloc_inst(segment.get_segment_manager());

IPCDataOffsetPtrQueueT *pIPCDataOffsetPtrQueue;
pIPCDataOffsetPtrQueue =
	segment.construct<IPCDataOffsetPtrQueueT>
		(channelName.c_str())(alloc_inst);


pIPCDataOffsetPtrQueue->push_back(ipcDataOffsetPtr );

并由进程B从通道中读取数据:

//bip::managed_shared_memory segment
//ipcDataOffsetPtr 

ipcDataOffsetPtr = pIPCDataOffsetPtrQueue->front();
IPCDataOffsetPtrQueue->pop_front();

cout <<ipcDataOffsetPtr->name << endl;

//此后ipcData需要通过destroy_ptr进行内存释放
segment.destroy_ptr(ipcDataOffsetPtr->get());

使用完的匿名共享内存对象,则通过共享内存池的destroy_ptr完成内存释放。

2.2 基于内存句柄的共享数据使用

上述匿名对象的使用需要通过offset_ptr作为跨越进程的桥梁,在两个进程间传输offset_ptr,之后再通过offset_ptr间接的获取到对应的共享内存对象。该方法能够达到数据共享的目的,但有两点不足:1). 该方法似乎有点绕,不够直观; 2). 改方法由于传递的是偏移指针,该指针并非朴素类型(POD),无法使用boost的无所队列进行传递。是否有更简单的方法,使得我们可以有更加方便的方式在进程间共享数据呢?答案是:可以通过boost::interprocess::managed_shared_memory::handle。通过handle,我们可以方便的还原出该handle对应对象的指针,同时,通过指针我们也能够方便的或者改指针对应的handle。重要的是,而实际上,handle就是朴素类型的int, 我们能够方便的将其通过无锁队列的Queue由一个进程推送到另外一个进程,进而达到在进程间高效共享数据的目的。下面我们来看看基于handle+lockfree::queue来完成进程间数据传输。

(1). 简单定义我们需要在进程间共享的数据结构

#ifndef __IPC_HANDLE_DATA_H__
#define __IPC_HANDLE_DATA_H__

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>

namespace bip = boost::interprocess;   //给ipc设置别名

//首先定义共享内存分配器类型
typedef bip::allocator<char, bip::managed_shared_memory::segment_manager> CharAllocator;
typedef bip::basic_string<char, std::char_traits<char>, CharAllocator> shared_string;


//用于IPCHandle传输
class IPCHandleData {
public:
	IPCHandleData(const shared_string::allocator_type& al);
	~IPCHandleData();

	int id;
	__int64 id64;
	shared_string name;

};
#endif

该数据类型包含3个字段:id、id64和name

(2). 定义我们需要用到的共享内存句柄类型,以及共享内存中定义无锁队列queue需要的分配类型

//定义共享内存handle类型
typedef bip::managed_shared_memory::handle_t IPCHandleT;

//定义该handle的共享内存分配器
typedef bip::allocator<IPCHandleT, 
			bip::managed_shared_memory::segment_manager> 
								MyIPCHandleAllocatorT;

//定义基于该IPCHandle以及对应的内存分配器的Queue
typedef boost::lockfree::queue<IPCHandleT, 
			boost::lockfree::capacity<65534>,  //最多时2^16-2
			boost::lockfree::allocator<MyIPCHandleAllocatorT>> 
								IPCHandleMwMrQueueT;

(3). 由一个创建者(Creator)负责创建共享内存,并创建用于传输的无锁队列

void Creator()
{
	cout << "Creator" << endl;

	// 删除遗留的共享内存
	bip::shared_memory_object::remove("MySharedMemory");

	// 构建新的托管共享内存区
	bip::managed_shared_memory segment(bip::create_only,
		"MySharedMemory",  //segment name
		65536 * 1024 * 10);

	// 定义托管共享内存区的分配器
	const MyIPCHandleAllocatorT alloc_inst(segment.get_segment_manager());

	try
	{
	
	// 创建共享内存中的用于传递IPCHandleT的无锁队列
	IPCHandleMwMrQueueT *pMyIPCHandleQueue =
		segment.construct<IPCHandleMwMrQueueT>("MyIPCHandleQueue")(alloc_inst);
	}
	catch (exception &e)
	{
		cout << e.what() << endl;
	}

	cout << "Creator is waiting here..." << endl;
	system("pause");

	
}

(4). 生产者一方首先附接到共享内存

进一步找到对应的无锁队列。基于共享内存,创建待传输的共享内存对象,并找到该共享内存对象的句柄handle,将该句柄handle放入到无锁队列中,完成传输动作

void Producer()
{
	cout << "Producer" << endl;

	//附接目标共享内存
	bip::managed_shared_memory segment(bip::open_only,
				"MySharedMemory");  //segment name
											
	typedef std::pair<IPCHandleMwMrQueueT *, 
			bip::managed_shared_memory::size_type> ResultT;
	ResultT res = segment.find<IPCHandleMwMrQueueT>("MyIPCHandleQueue");

	cout << "res.second=" << res.second << endl;
	if (!res.second)
	{
		cout << "could not find \"MyIPCHandleQueue\"" << endl;
		return;
	}
	else
	{
		cout << "find \"MyIPCHandleQueue\"" << endl;
	}

	IPCHandleMwMrQueueT *pMyIPCHandleQueue = res.first;

	int id = 1;
	
	//创建匿名共享内存对象,销毁时需要用destroy_ptr
	IPCHandleData *pIPCHandleData =
 		segment.construct<IPCHandleData>
			(bip::anonymous_instance)
				(shared_string::allocator_type(segment.get_segment_manager()));
	pIPCHandleData->id = id;
	pIPCHandleData->id64 = id;

	stringstream ss;
	ss << "name-" << id;
	pIPCHandleData->name = ss.str().c_str();

	cout << "cur IPCHandleData :" << endl;
	cout << "id=" << pIPCHandleData->id << endl;
	cout << "id64=" << pIPCHandleData->id64 << endl;
	cout << "name=" << pIPCHandleData->name << endl;

	//获取共享内存对应的handle
	IPCHandleT curHandle = segment.get_handle_from_address(pIPCHandleData);
	cout << "handle=" << (int)(curHandle) << endl;

	//将该handle放入queue中,传递给对端
	pMyIPCHandleQueue->push(curHandle);

}

(5). 消费者,同样附接到共享内存

并找到对应的Queue,从Queue中pop出handle数据,基于该handle,找到对应的共享内存指针,进而获取共享内存对象的信息

void Consumer()
{
	cout << "Consumer" << endl;

	//附接目标共享内存
	bip::managed_shared_memory segment(bip::open_only,
		"MySharedMemory");  //segment name
		
	typedef std::pair<IPCHandleMwMrQueueT *, 
				bip::managed_shared_memory::size_type> ResultT;
	ResultT res = segment.find<IPCHandleMwMrQueueT>("MyIPCHandleQueue");

	cout << "res.second=" << res.second << endl;
	if (!res.second)
	{
		cout << "could not find \"MyIPCHandleQueue\"" << endl;
		return;
	}
	else
	{
		cout << "find \"MyIPCHandleQueue\"" << endl;
	}

	IPCHandleMwMrQueueT *pMyIPCHandleQueue = res.first;

	IPCHandleT recvHandle;
	if (pMyIPCHandleQueue->pop(recvHandle))
	{
		cout << "Recv data" << endl;

		//接收到消息后,通过handle找到对应的共享内存对象
		IPCHandleData *pIPCHandleData = 
			(IPCHandleData *)segment.get_address_from_handle(recvHandle);

		//输出共享内存对象信息
		cout << "recved IPCHandleData :" << endl;
		cout << "id=" << pIPCHandleData->id << endl;
		cout << "id64=" << pIPCHandleData->id64 << endl;
		cout << "name=" << pIPCHandleData->name << endl;

		//最终释放该共享内存对象
		segment.destroy_ptr(pIPCHandleData);
	}
}

 

© 著作权归作者所有

共有 人打赏支持
lday
粉丝 0
博文 3
码字总数 13046
作品 0
浦东
程序员
私信 提问
使用 Boost 的 IPC 和 MPI 库进行并发编程

文章转自 IBM developerWorks 使用非常流行的 Boost 库进行并发编程非常有意思。Boost 有几个用于并发编程领域的库:Interprocess (IPC) 库用于实现共享内存、内存映射的 I/O 和消息队列;T...

IBMdW
2011/06/08
2.7K
0
Linux — IPC通信之共享内存

共享内存 共享内存是三个IPC机制中的一个。它允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在进行的进程之 间传递数据的一种非常有效的方式。大多数的共享内存的实现,都把由...

Dawn_sf
2017/07/02
0
0
Linux进程间通信源码剖析,共享内存(shmget函数详解)

shmget int shmget(key_t key, size_t size, int flag); key: 标识符的规则 size:共享存储段的字节数 flag:读写的权限 返回值:成功返回共享存储的id,失败返回-1 key_t key --------------...

长平狐
2012/06/12
1K
0
进程间通信学习小结(共享内存)

要使用共享内存,应该有如下步骤: 1.开辟一块共享内存 shmget() 2.允许本进程使用共某块共享内存 shmat() 3.写入/读出 4.禁止本进程使用这块共享内存 shmdt() 5.删除这块共享内存 shmctl()...

晨曦之光
2012/03/09
2.6K
0
linux/unix下多进程间的通信

进程:进程是计算机运行的基本单位,利用多进程可以实现系统的多任务;但是,在多进程的任务中,进程之间的通信是比较麻烦的,因为,进程之间使用的是不同的进程空间,所以,编写多进程的系统...

陈小花与胡汉三
2013/12/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

开启genelog

打开genelog genelog 可以记录数据库的操作语句。 首先进入mysql, mysql -u root -p 设置日志位置 set global general_log_file = "/tmp/general.log"; 设置全局global模式 set global gen......

狼王黄师傅
4分钟前
0
0
Java 帝国对 Python 的渗透能成功吗?哈哈

引子 Java 帝国已经成立20多年,经过历代国王的励精图治,可以说是地大物博,码农众多。 可是国王依然不满足,整天想着如何继续开拓疆土, 这一天晚上他又把几个重臣招来商议了。 IO大臣说:...

边鹏_尛爺鑫
今天
5
0
分布式事务解决方案框架(LCN)

什么是XA接口 XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口...

群星纪元
今天
6
0
linux 操作系统 常用命令和软件安装

1.系统时间更新 ntpdate time.windows.com 2.传送文件 rsync -av /home/data/a.dat -e ssh root@192.168.0.100:/home 3.传送文件夹 scp -r /home/data root@192.168.0.100:/home 4.JDK安装 ......

WJtiny
今天
2
0
pg_lightool基于basebackup的单表恢复和块恢复

开源软件pg_lightool,实现了基于wal日志的块恢复。详情参见博客:https://my.oschina.net/lcc1990/blog/1931485。由于wal日志中FPW的不确定性,它不能作为一个数据库恢复的解决方案。目前对...

movead
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部