问题
Q: 如何用共享内存来存放C++ STL中中的容器?
A: 传入自定义的申请共享内存上空间的allocator,见模板参数Allocator
参考
https://www.zhihu.com/question/319108981/answer/649050789
https://en.cppreference.com/w/cpp/container/vector
http://www.cppblog.com/doing5552/archive/2010/07/24/121197.html
文章目录
- 问题
- 参考
- 分析内容
- 调用POSIX 函数申请共享内存示例
- V系统调用的方式申请共享内存
- 存放在共享内存中的数据结构应该简单
- 共享内存中的STL容器,有困难
- 重用STL allocator,传入模板参数Allocator,可以实现
- 结论:只要提供一个用户自定义的allocator,任何STL容器都可以安全的放置到共享内存上。
- 正式干活
- 1、设计一个 allocator
- 2、容器在共享内存中的确定地址
- 2.1、方式1
- 2.2、方式2
- 3 代码资源
分析内容
共享内存(shm)允许多个进程把同一块物理内存段(segment)映射(map)到它们的地址空间中去。顾名思义,共享内存就是进程之间共享的一组内存段。当一个进程附着到一块共享内存上后,它得到一个指向这块共享内存的指针;该进程可以像使用其他内存一样使用这块共享内存。当然,由于这块内存同样会被其他进程访问或写入,所以必须要注意进程同步问题。
参考如下代码,这是UNIX系统上使用共享内存的一般方法(注:本文调用的是POSIX函数):
调用POSIX 函数申请共享内存示例
可以看在上一篇博客多线程之间、使用共享内存、实现图片的数据通信:包括POSIX共享内存(shm_open 和 mmap虚拟内存)、系统调用(shmat物理存储器)、内存映射文件等方法中说了,处理POSIX以外,还可以使用系统调用。
V系统调用的方式申请共享内存
//Get shared memory id
//shared memory key
const key_t ipckey = 24568;
//shared memory permission; can be
//read and written by anybody
const int perm = 0666;
//shared memory segment size
size_t shmSize = 4096;
//Create shared memory if not
//already created with specified
//permission
int shmId = shmget(ipckey,shmSize,IPC_CREAT|perm);
if (shmId ==-1)
{//Error
}//Attach the shared memory segmentvoid* shmPtr = shmat(shmId,NULL,0);struct commonData* dp = (struct commonData*)shmPtr;//detach shared memory
shmdt(shmPtr);
存放在共享内存中的数据结构应该简单
按照博客的说法,结构 commonData 的成员 name 和指向下一个结构的 next 所指向的内存分别从进程A的地址空间中的堆上分配,这种方式无法在另外一个进程使用,当进程B访问 dp->name 或者 dp->next 时候,由于它在访问自己地址空间以外的内存空间,所以这将是非法操作(memory violation),它无法正确得到 name和 next 所指向的内存。
如下结构:
struct commonData
{int sharedInt;float sharedFloat;char* name;Struct CommonData* next;
};
进程A把数据写入共享内存:
//Attach shared memory
struct commonData* dp = (struct commonData*)shmat(shmId,NULL,0);dp->sharedInt = 5;
.
.
dp->name = new char [20];
strcpy(dp->name,"My Name");dp->next = new struct commonData();
稍后,进程B把数据读出:
struct commonData* dp = (struct commonData*)shmat(shmId,NULL,0);//count = 5;
int count = dp->sharedInt;
//problem
printf("name = [%s]\n",dp->name);
dp = dp->next; //problem
结论:放入共享内存中的结构应该简单。(注:我觉得最好避免使用指针)
共享内存中的STL容器,有困难
如果把STL容器,例如map, vector, list放入共享内存中,就没必要再为共享内存设计其他额外的数据结构。STL容器被良好的封装,默认情况下有它们自己的内存管理方案。当一个元素被插入到一个STL列表(list)中时,列表容器自动为其分配内存,保存数据。考虑到要将STL容器放到共享内存中,而容器却自己在堆上分配内存,下面的实验证明了直接在共享内存放容器搞不来,会报错:
//Attach to shared memory
void* rp = (void*)shmat(shmId,NULL,0);
//Construct the vector in shared memory using placement new
vector<int>* vpInA = new(rp) vector<int>*;
//The vector is allocating internal data
//from the heap in process A's address
//space to hold the integer value
(*vpInA)[0] = 22;
然后进程B希望从共享内存中取出数据:
vector<int>* vpInB = (vector<int>*) shmat(shmId,NULL,0);//problem - the vector contains internal
//pointers allocated in process A's address
//space and are invalid here
int i = *(vpInB)[0];
矢量包含在进程A的地址空间中分配的内部指针,在此处无效。
重用STL allocator,传入模板参数Allocator,可以实现
传入自定义的申请共享内存上空间的allocator,见模板参数Allocator
进一步考察STL容器,我们发现它的模板定义中有第二个默认参数,也就是allocator 类,该类实际是一个内存分配模型。默认的allocator是从堆上分配内存(注:这就是STL容器的默认表现,我们甚至可以改造它从一个网络数据库中分配空间,保存数据)。下边是 vector 类的一部分定义:
template<class T, class A = allocator<T> >
class vector
{//other stuff
};
考虑如下声明:
//User supplied allocator myAlloc
vector<int,myAlloc<int> > alocV;
假设 myAlloc 从共享内存上分配内存,则 alocV 将完全在共享内存上被构造,所以进程A可以如下:
//Attach to shared memory
void* rp = (void*)shmat(shmId,NULL,0);
//Construct the vector in shared memory
//using placement new
vector<int>* vpInA =new(rp) vector<int,myAlloc<int> >*;
//The vector uses myAlloc<int> to allocate
//memory for its internal data structure
//from shared memory
(*v)[0] = 22;
进程B可以如下读出数据:
vector<int>* vpInB = (vector<int,myAlloc<int> >*) shmat(shmId,NULL,0);//Okay since all of the vector is
//in shared memory
int i = *(vpInB)[0];
所有附着在共享内存上的进程都可以安全的使用该vector。在这个例子中,该类的所有内存都在共享内存上分配,同时可以被其他的进程访问。只要提供一个用户自定义的allocator,任何STL容器都可以安全的放置到共享内存上。
结论:只要提供一个用户自定义的allocator,任何STL容器都可以安全的放置到共享内存上。
正式干活
1、设计一个 allocator
基于共享内存的STL Allocator,shared_allocator.hh 是一个STL Allocator的实现,SharedAllocator 是一个模板类。而 Pool 类完成共享内存的分配与回收。
您所描述的pool.hh
和shmPool
类似乎是用于管理共享内存的一个封装。下面我会基于您给出的描述来详细解释各个部分:
-
Pool 类定义
Pool
是更广泛的类
shmPool
是Pool
的一个具体实现 -
静态成员 shm_
shm_
是一个静态成员,其类型是shmPool
。- 静态成员意味着它在所有类的实例之间共享,并且只存在一个副本。因此,不论你创建了多少个
Pool
(或shmPool
)的实例,它们都共享同一个shm_
对象。 - 这确保了每个进程(如果每个进程都访问这个静态成员)都只有一个
shmPool
的实例,这对于共享内存的管理是非常重要的。
-
shmPool 构造函数
- 构造函数用于初始化
shmPool
对象。 - 在这个过程中,它会创建并附着所需大小的内存到共享内存上。
- 共享内存的参数(如键值、段数目、段大小)通过环境变量传递给构造函数。这样,你可以在不修改代码的情况下,通过调整环境变量来改变共享内存的行为。
- 构造函数用于初始化
-
成员变量
segs_
:表示共享段的数目。segSize_
:表示每个共享段的大小。path_
和key_
:用于创建唯一的IPC键(可能是POSIX IPC或System V IPC)。这个键用于标识和访问共享内存。
-
信号量(Semaphore)
shmPool
为每个共享段创建一个信号量,用于同步。这意味着当多个进程或线程需要访问同一个共享段时,它们会通过信号量来协调,以确保数据的一致性和完整性。
-
Chunk 类
Chunk
类代表一个共享段。这意味着shmPool
可能包含多个Chunk
对象,每个对象管理共享内存中的一个段。- 每个
Chunk
都有一个唯一的shmId_
(共享内存ID),用于标识和访问该段。 semId_
是一个信号量ID,用于控制对该段的访问许可。Link
结构(或类的指针)可能用于表示该Chunk
中可用的内存块或段的链接列表。这样,当你需要分配或释放内存时,你可以通过这个链接列表来有效地管理内存。
shmPool
类似乎是一个封装了共享内存管理的类,它使用静态成员来确保每个进程只有一个实例,使用信号量来同步访问,并使用Chunk
类来管理共享内存的各个段。这种设计使得在多进程或多线程环境中使用共享内存变得更加简单和安全。
2、容器在共享内存中的确定地址
2.1、方式1
假设进程A在共享内存中放入了数个容器,进程B如何找到这些容器呢?一个方法就是进程A把容器放在共享内存中的确定地址上(fixed offsets),则进程B可以从该已知地址上获取容器。
2.2、方式2
假设进程A在共享内存中放入了数个容器,进程B如何找到这些容器呢?一个方法,进程A先在共享内存某块确定地址上放置一个map容器,然后进程A再创建其他容器,然后给其取个名字和地址一并保存到这个map容器里。进程B知道如何获取该保存了地址映射的map容器,然后同样再根据名字取得其他容器的地址。
3 代码资源
本文描述的方案可以在共享内存中创建STL容器,其中的一个缺陷是,在分配共享内存之前,应该保证共享内存的总大小(segs_* segSize_)大于你要保存STL容器的最大长度,因为一旦类Pool 超出了共享内存的,该类无法再分配新的共享内存。
见绑定资源