昨天在上篇blog里描写了如何把STL容器放到共享内存里去,不过由于好久不写blog,发觉词汇组织能力差了很多,不少想写的东西写的很零散,今天刚好翻看自己的书签,看到一篇挺老的文章,不过从共享内存到STL容器讲述得蛮全面,还提供了学习的实例,所以顺便翻译过来,并附上原文地址

共享内存(shm)是当前主流UNIX系统中的一种IPC方法,它允许多个进程把同一块物理内存段(segment)映射(map)到它们的地址空间中去。既然内存段对于各自附着(attach)的进程是共享的,这些进程可以很方便的通过这块共享内存上的共有数据进行通信。因此,顾名思义,共享内存就是进程之间共享的一组内存段。当一个进程附着到一块共享内存上后,它得到一个指向这块共享内存的指针;该进程可以像使用其他内存一样使用这块共享内存。当然,由于这块内存同样会被其他进程访问或写入,所以必须要注意进程同步问题。

参考如下代码,这是UNIX系统上使用共享内存的一般方法(注:本文调用的是POSIX函数):

  1. //Get shared memory id
  2. //shared memory key
  3. const key_t ipckey = 24568;
  4. //shared memory permission; can be
  5. //read and written by anybody
  6. const int perm = 0666;
  7. //shared memory segment size
  8. size_t shmSize = 4096;
  9. //Create shared memory if not
  10. //already created with specified
  11. //permission
  12. int shmId = shmget
  13. (ipckey,shmSize,IPC_CREAT|perm);
  14. if (shmId ==-1) {
  15. //Error
  16. }
  17.  
  18. //Attach the shared memory segment
  19.  
  20. void* shmPtr = shmat(shmId,NULL,0);
  21.  
  22. struct commonData* dp = (struct commonData*)shmPtr;
  23.  
  24. //detach shared memory
  25. shmdt(shmPtr);

存放在共享内存中的数据结构

当保存数据到共享内存中时需要留意,参考如下结构:

  1. struct commonData {
  2. int sharedInt;
  3. float sharedFloat;
  4. char* name;
  5. Struct CommonData* next;
  6. };

进程A把数据写入共享内存:

  1. //Attach shared memory
  2. struct commonData* dp =
  3. (struct commonData*) shmat
  4. (shmId,NULL,0);
  5.  
  6. dp->sharedInt = 5;
  7. .
  8. .
  9. dp->name = new char [20];
  10. strcpy(dp->name,"My Name");
  11.  
  12. dp->next = new struct commonData();

稍后,进程B把数据读出:

  1. struct commonData* dp =
  2. (struct commonData*) shmat
  3. (shmId,NULL,0);
  4.  
  5. //count = 5;
  6. int count = dp->sharedInt;
  7. //problem
  8. printf("name = [%s]\n",dp->name);
  9. dp = dp->next; //problem

结构 commonData 的成员 name 和指向下一个结构的 next 所指向的内存分别从进程A的地址空间中的堆上分配,显然 name 和 next 指向的内存也只有进程A可以访问。当进程B访问 dp->name 或者 dp->next 时候,由于它在访问自己地址空间以外的内存空间,所以这将是非法操作(memory violation),它无法正确得到 name和 next 所指向的内存。因此,所有的共享内存中的指针必须同样指向共享内存中的地址。(这也是为什么包含虚函数继承的C++类对象不能放到共享内存中的原因——这是另外一个话题。注:因为虚函数的具体实现可能会在其他的内存空间中)由于这些条件限制,放入共享内存中的结构应该简单简单。(注:我觉得最好避免使用指针)

共享内存中的STL容器

想像一下把STL容器,例如map, vector, list等等,放入共享内存中,IPC一旦有了这些强大的通用数据结构做辅助,无疑进程间通信的能力一下子强大了很多。我们没必要再为共享内存设计其他额外的数据结构,另外,STL的高度可扩展性将为IPC所驱使。STL容器被良好的封装,默认情况下有它们自己的内存管理方案。当一个元素被插入到一个STL列表(list)中时,列表容器自动为其分配内存,保存数据。考虑到要将STL容器放到共享内存中,而容器却自己在堆上分配内存。一个最笨拙的办法是在堆上构造STL容器,然后把容器复制到共享内存,并且确保所有容器的内部分配的内存指向共享内存中的相应区域,这基本是个不可能完成的任务。例如下边进程A所做的事情:

  1. //Attach to shared memory
  2. void* rp = (void*)shmat(shmId,NULL,0);
  3. //Construct the vector in shared
  4. //memory using placement new
  5. vector<int>* vpInA = new(rp) vector<int>*;
  6. //The vector is allocating internal data
  7. //from the heap in process A's address
  8. //space to hold the integer value
  9. (*vpInA)[0] = 22;

然后进程B希望从共享内存中取出数据:

  1. vector<int>* vpInB =
  2. (vector<int>*) shmat(shmId,NULL,0);
  3.  
  4. //problem - the vector contains internal
  5. //pointers allocated in process A's address
  6. //space and are invalid here
  7. int i = *(vpInB)[0];

重用STL allocator

进一步考察STL容器,我们发现它的模板定义中有第二个默认参数,也就是allocator 类,该类实际是一个内存分配模型。默认的allocator是从堆上分配内存(注:这就是STL容器的默认表现,我们甚至可以改造它从一个网络数据库中分配空间,保存数据)。下边是 vector 类的一部分定义:

  1. template<class T, class A = allocator<T> >
  2. class vector {
  3. //other stuff
  4. };

考虑如下声明:

  1. //User supplied allocator myAlloc
  2. vector<int,myAlloc<int> > alocV;

假设 myAlloc 从共享内存上分配内存,则 alocV 将完全在共享内存上被构造,所以进程A可以如下:

  1. //Attach to shared memory
  2. void* rp = (void*)shmat(shmId,NULL,0);
  3. //Construct the vector in shared memory
  4. //using placement new
  5. vector<int>* vpInA =
  6. new(rp) vector<int,myAlloc<int> >*;
  7. //The vector uses myAlloc<int> to allocate
  8. //memory for its internal data structure
  9. //from shared memory
  10. (*v)[0] = 22;

进程B可以如下读出数据:

  1. vector<int>* vpInB =
  2. (vector<int,myAlloc<int> >*) shmat
  3. (shmId,NULL,0);
  4.  
  5. //Okay since all of the vector is
  6. //in shared memory
  7. int i = *(vpInB)[0];

所有附着在共享内存上的进程都可以安全的使用该vector。在这个例子中,该类的所有内存都在共享内存上分配,同时可以被其他的进程访问。只要提供一个用户自定义的allocator,任何STL容器都可以安全的放置到共享内存上。

一个基于共享内存的STL Allocator

清单 shared_allocator.hh 是一个STL Allocator的实现,SharedAllocator 是一个模板类。而 Pool 类完成共享内存的分配与回收。

  1. template<class T>class SharedAllocator {
  2. private:
  3. Pool pool_; // pool of elements of sizeof(T)
  4. public:
  5. typedef T value_type;
  6. typedef unsigned int size_type;
  7. typedef ptrdiff_t difference_type;
  8. typedef T* pointer;
  9. typedef const T* const_pointer;
  10. typedef T& reference;
  11. typedef const T& const_reference;
  12. pointer address(reference r) const { return &r; }
  13. const_pointer address(const_reference r) const {return &r;}
  14. SharedAllocator() throw():pool_(sizeof(T)) {}
  15. template<class U> SharedAllocator
  16. (const SharedAllocator<U>& t) throw():
  17. pool_(sizeof(T)) {}
  18. ~SharedAllocator() throw() {};
  19. // space for n Ts
  20. pointer allocate(size_t n, const void* hint=0)
  21. {
  22. return(static_cast<pointer> (pool_.alloc(n)));
  23. }
  24. // deallocate n Ts, don't destroy
  25. void deallocate(pointer p,size_type n)
  26. {
  27. pool_.free((void*)p,n);
  28. return;
  29. }
  30. // initialize *p by val
  31. void construct(pointer p, const T& val) { new(p) T(val); }
  32. // destroy *p but don't deallocate
  33. void destroy(pointer p) { p->~T(); }
  34. size_type max_size() const throw()
  35. {
  36. pool_.maxSize();
  37. }
  38. template<class U>
  39. // in effect: typedef SharedAllocator<U> other
  40. struct rebind { typedef SharedAllocator<U> other; };
  41. };
  42.  
  43. template<class T>bool operator==(const SharedAllocator<T>& a,
  44. const SharedAllocator<T>& b) throw()
  45. {
  46. return(a.pool_ == b.pool_);
  47. }
  48. template<class T>bool operator!=(const SharedAllocator<T>& a,
  49. const SharedAllocator<T>& b) throw()
  50. {
  51. return(!(a.pool_ == b.pool_));
  52. }

清单pool.hh是 Pool 类定义,其中静态成员shm_ 是类型 shmPool,保证每个进程只有唯一的一个shmPool 实例。shmPool ctor 创建并附着所需大小的内存到共享内存上。共享内存的参数,比如 键值、段数目、段大小,都通过环境变量传递给 shmPool ctor。成员 segs_ 是共享段的数目,segSize_是每个共享段的大小,成员path_key_ 用来创建唯一的 ipckeyshmPool 为每个共享段创建一个信号量(semaphore)用于同步。shmPool 还在为每个共享段构造了一个 Chunk 类,一个 Chunk代表一个共享段。每个共享段的标识是shmId_, 信号量 semId_控制该段的访问许可,一个指向 Link 结构的指针表明 Chunk类的剩余列表。

  1. class Pool {
  2. private:
  3. class shmPool {
  4. private:
  5. struct Container {
  6. containerMap* cont;
  7. };
  8. class Chunk {
  9. public:
  10. Chunk()
  11. Chunk(Chunk&);
  12. ~Chunk() {}
  13. void* alloc(size_t size);
  14. void free (void* p,size_t size);
  15. private:
  16. int shmId_;
  17. int semId_;
  18. int lock_()
  19. };
  20. int key_;
  21. char* path_;
  22. Chunk** chunks_;
  23. size_t segs_;
  24. size_t segSize_;
  25. Container* contPtr_;
  26. int contSemId_;
  27. public:
  28. shmPool();
  29. ~shmPool();
  30. size_t maxSize();
  31. void* alloc(size_t size);
  32. void free(void* p, size_t size);
  33. int shmPool::lockContainer()
  34. int unLockContainer()
  35. containerMap* getContainer()
  36. void shmPool::setContainer(containerMap* container)
  37. };
  38.  
  39. private:
  40. static shmPool shm_;
  41. size_t elemSize_;
  42. public:
  43. Pool(size_t elemSize);
  44. ~Pool() {}
  45. size_t maxSize();
  46. void* alloc(size_t size);
  47. void free(void* p, size_t size);
  48. int lockContainer();
  49. int unLockContainer();
  50. containerMap* getContainer();
  51. void setContainer(containerMap* container);
  52. };
  53. inline bool operator==(const Pool& a,const Pool& b)
  54. {
  55. return(a.compare(b));
  56. }

把STL容器放入共享内存

假设进程A在共享内存中放入了数个容器,进程B如何找到这些容器呢?一个方法就是进程A把容器放在共享内存中的确定地址上(fixed offsets),则进程B可以从该已知地址上获取容器。另外一个改进点的办法是,进程A先在共享内存某块确定地址上放置一个map容器,然后进程A再创建其他容器,然后给其取个名字和地址一并保存到这个map容器里。进程B知道如何获取该保存了地址映射的map容器,然后同样再根据名字取得其他容器的地址。清单container_factory.hh是一个容器工厂类。类Pool的方法setContainer把map容器放置在一个已知地址上,方法getContainer可以重新获取这个map。该工厂的方法用来在共享内存中创建、获取和删除容器。当然,传递给容器工厂的容器需要以SharedAllocator作为allocator。

  1. struct keyComp {
  2. bool operator()(const char* key1,const char* key2)
  3. {
  4. return(strcmp(key1,key2) < 0);
  5. }
  6. };
  7. class containerMap: public map<char*,void*,keyComp,SharedAllocator<char* > > {};
  8. class containerFactory {
  9. public:
  10. containerFactory():pool_(sizeof(containerMap)){}
  11. ~containerFactory() {}
  12. template<class Container> Container* createContainer
  13. (char* key,Container* c=NULL);
  14. template<class Container> Container* getContainer
  15. (char* key,Container* c=NULL);
  16. template<class Container> int removeContainer
  17. (char* key,Container* c=NULL);
  18. private:
  19. Pool pool_;
  20. int lock_();
  21. int unlock_();
  22. };

结论

本文描述的方案可以在共享内存中创建STL容器,其中的一个缺陷是,在分配共享内存之前,应该保证共享内存的总大小(segs_* segSize_)大于你要保存STL容器的最大长度,因为一旦类Pool 超出了共享内存的,该类无法再分配新的共享内存。

完整的源代码可以从这里下载:www.cuj.com/code

参考文献

  • Bjarne Stroustrup. The C++ Programming Language, Third Edition (Addison-Wesley, 1997).
  • Matthew H. Austern. Generic Programming and the STL: Using and
    Extending the C++ Standard Template Library (Addison-Wesley, 1999).

关于作者

Grum Ketema has Masters degrees in Electrical Engineering and Computer Science. With 17 years of experience in software development, he has been using C since 1985, C++ since 1988, and Java since 1997. He has worked at AT&T Bell Labs, TASC, Massachusetts Institute of Technology, SWIFT, BEA Systems, and Northrop.

vector存入共享内存(了解)的更多相关文章

  1. 例程使用(1-4)共享内存 存图片+vector容器教程

    1传输的数据 1-1数据格式说明 1 两路视频图像Mat 图像 图像数据(Mat)+图像头信息(ImgInf) //图像的宽.高.类型信息 typedef struct { int width; // ...

  2. Linux Linux程序练习十五(进程间的通信共享内存版)

    /* * 题目: * 编写程序,要去实现如下功能: 父进程创建子进程1和子进程2.子进程1向子进程2发送可靠信号,并传送额外数据为子进程1的pid*2; 子进程2接受可靠信号的值,并发送给父进程,父进 ...

  3. php简单使用shmop函数创建共享内存减少服务器负载

    在之前的一篇博客[了解一下共享内存的概念及优缺点]已经对共享内存的概念做了说明.下面就来简单使用共享内存(其实也可以用其他工具,比如redis) PHP做内存共享有两套接口.一个是shm,它实际上是变 ...

  4. string为什么可以写入共享内存

    我今天在想这个vector,map为什么不能写入共享内存,原来是因为new的时候只是new了这个对象在共享内存上,而真正的堆上的内存并没有在共享内存里面的,如果要想vector 可以共享就要重写分配器 ...

  5. boost进程间通信经常使用开发一篇全(消息队列,共享内存,信号)

    本文概要: 敏捷开发大家想必知道并且评价甚高,缩短开发周期,提高开发质量.将大project独立为不同的小app开发,整个开发过程,程序可用可測,所以提高了总体的质量.基于这样的开发模式和开发理念,进 ...

  6. System V IPC 之共享内存

    IPC 是进程间通信(Interprocess Communication)的缩写,通常指允许用户态进程执行系列操作的一组机制: 通过信号量与其他进程进行同步 向其他进程发送消息或者从其他进程接收消息 ...

  7. ubuntu linux c学习笔记----共享内存(shmget,shmat,shmdt,shmctl)

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

  8. Linux进程间通信(System V) --- 共享内存

    共享内存 IPC 原理 共享内存进程间通信机制主要用于实现进程间大量的数据传输,下图所示为进程间使用共享内存实现大量数据传输的示意图: 共享内存是在内存中单独开辟的一段内存空间,这段内存空间有自己特有 ...

  9. Boost:shared_memory_object --- 共享内存

    什么是共享内存 共享内存是最快速的进程间通信机制.操作系统在几个进程的地址空间上映射一段内存,然后这几个进程可以在不需要调用操作系统函数的情况下在那段内存上进行读/写操作.但是,在进程读写共享内存时, ...

随机推荐

  1. Thinkphp 下面执行crond

    thinkphp开启cli支持  1.tp正好支持cli命令模式,手册的路径为13.7.4 如果是用的其他框架不支持cli,那么只能直接写程序了,其实就是写面向过程的最基础的php代码. 2.在入口文 ...

  2. 怎样使android的view动画循环弹动

    在res中建立文件夹anim,分别写下cycles.xml,shake1.xml,shake2.xml cycles.xml: <?xml version="1.0" enc ...

  3. Android之Selector、Shape介绍

    ------------整理自网络---------------------- <?xml version=”1.0″ encoding=”utf-8″?> <shape xmlns ...

  4. 委托、匿名函数与Lambda表达式初步

    (以下内容主要来自<C#本质论第三版>第十二章委托和Lambda表达式) 一.委托续 上上周五看了看委托,初步明白了其是个什么,如何定义并调用.上周五准备看Lambda表达式,结果发现C# ...

  5. STM32F4_引领入门

    Ⅰ.概述 该文写给那些想学ST芯片开发(或初级学习)的朋友,文章着重细节,或许有点简单. 笔者想告诉那些刚开始学习ST的朋友,不管你使用哪一个系列(F0.F1.F2),哪一种型号芯片,其实学习的方法和 ...

  6. SSD1306驱动的OLED实验

    [转]http://bbs.21ic.com/icview-434543-1-1.html 前面几章的实例,均没涉及到液晶显示,这一章,我们将向大家介绍OLED的使用.在本章中,我们将使用战舰STM3 ...

  7. (转)Android网络命令

    转自:http://www.cnblogs.com/shunyao8210/archive/2010/08/10/1796214.html ifconfig 1.       作用 ifconfig用 ...

  8. Linux分区

    硬盘分区主要分为基本分区和扩展分区两种,基本分区和扩展分区的数目之和不能大于四个.且基本分区可以马上被使用但不能再分区.扩展分区必须再进行分区后才能进行使用,也就是说它必须进行二次分区.扩展分区再分下 ...

  9. 九度oj 1523 从上往下打印二叉树

    原题链接:http://ac.jobdu.com/problem.php?pid=1523 建树,再层次遍历bfs.为了找根方便些,加了father指针... 如下: #include<algo ...

  10. WdatePicker 动态变量表

    4. 日期范围限制静态限制 注意:日期格式必须与 realDateFmt 和 realTimeFmt 一致 你可以给通过配置minDate(最小日期),maxDate(最大日期)为静态日期值,来限定日 ...