什么是共享内存
共享内存是最快速的进程间通信机制。操作系统在几个进程的地址空间上映射一段内存,然后这几个进程可以在不需要调用操作系统函数的情况下在那段内存上进行读/写操作。但是,在进程读写共享内存时,我们需要一些同步机制。
考虑一下服务端进程使用网络机制在同一台机器上发送一个HTML文件至客户端将会发生什么:
- 服务端必须读取这个文件至内存,然后将其传至网络函数,这些网络函数拷贝那段内存至操作系统的内部内存。
- 客户端使用那些网络函数从操作系统的内部内存拷贝数据至它自己的内存。
如上所示,这里存在两次拷贝,一次是从内存至网络,另一次是从网络至内存。这些拷贝使用操作系统调度,这往往开销比较大。共享内存避免了这种开销,但是我们需要在进程间同步:
- 服务端映射一个共享内存至其地址空间,并且获取同步机制。服务端使用同步机制获取对这段内存的独占访问,并且拷贝文件至这段内存中。
- 客户端映射这个共享内存至其地址空间。等待服务端释放独占访问,然后使用数据。
使用共享内存,我们能够避免两次数据拷贝,但是我们必须同步对共享内存段的访问。
创建能在进程间共享的内存片段
为了使用共享内存,我们必须执行两个基本步骤:
- 向操作系统申请一块能在进程间共享的内存。使用者能够使用共享内存对象创建/销毁/打开这个内存:一个代表内存的对象,这段内存能同时被映射至多个进程的地址空间。
- 将这个内存的部分或全部与被调用进程的地址空间联系起来。操作系统在被调用进程的地址空间上寻找一块足够大的内存地址范围,然后将这个地址范围标记为特殊范围。在地址范围上的变化将会被另一个映射了同样的共享内存对象的进程自动监测到。
一旦成功完成了以上两步,进程可以开始在地址空间上读写,然后与另一个进程发送和接收数据。现在,我们看看如何使用Boost.Interprocess做这些事:
头文件
为了管理共享内存,你需要包含下面这个头文件:
- #include <boost/interprocess/shared_memory_object.hpp>
如上述,我们必须使用类 shared_memory_object 来创建、打开和销毁能被几个进程映射的共享内存段。我们可以指定共享内存对象的访问模式(只读或读写),就好像它是一个文件一样:
- 创建共享内存段。如果已经创建了,会抛异常:
- using boost::interprocess;
- shared_memory_object shm_obj
- (create_only //only create
- ,"shared_memory" //name
- ,read_write //read-write mode
- );
- 打开或创建一个共享内存段:
- using boost::interprocess;
- shared_memory_object shm_obj
- (open_or_create //open or create
- ,"shared_memory" //name
- ,read_only //read-only mode
- );
- 仅打开一个共享内存段。如果不存在,会抛异常:
- using boost::interprocess;
- shared_memory_object shm_obj
- (open_only //only open
- ,"shared_memory" //name
- ,read_write //read-write mode
- );
当一个共享内存对象被创建了,它的大小是0。为了设置共享内存的大小,使用者需在一个已经以读写方式打开的共享内存中调用truncate 函数:
shm_obj.truncate(10000);
因为共享内存具有内核或文件系统持久化性质,因此用户必须显式销毁它。如果共享内存不存在、文件被打开或文件仍旧被其他进程内存映射,则删除操作可能会失败且返回false:
- using boost::interprocess;
- shared_memory_object::remove("shared_memory");
更多关于shared_memory_object的详情,请参考 boost::interprocess::shared_memory_object。
映射共享内存片段
一旦被创建或打开,一个进程必须映射共享内存对象至进程的地址空间。使用者可以映射整个或部分共享内存。使用类mapped_region完成映射过程。这个类代表了一个内存区域,这个内存区域已经被从共享内存或其他映射兼容的设备(例如,文件)映射。一个mapped_region能从任何memory_mappable对象创建,所以如你想象,shared_memory_object就是一个memory_mappable对象:
- using boost::interprocess;
- std::size_t ShmSize = ...
- //Map the second half of the memory
- mapped_region region
- ( shm //Memory-mappable object
- , read_write //Access mode
- , ShmSize/2 //Offset from the beginning of shm
- , ShmSize-ShmSize/2 //Length of the region
- );
- //Get the address of the region
- region.get_address();
- //Get the size of the region
- region.get_size();
使用者可以从可映射的对象中指定映射区域的起始偏移量以及映射区域的大小。如果未指定偏移量或大小,则整个映射对象(在此情况下是共享内存)被映射。如果仅指定了偏移量而没有指定大小,则映射区域覆盖了从偏移量到可映射对象结尾的整个区域。
更多关于mapped_region的详情,请参考 boost::interprocess::mapped_region。
一个简单的例子
让我们看看一个简单的使用共享内存的例子。一个服务端进程创建了一个共享内存对象,映射它并且初始化所有字节至同一个值。之后,客户端进程打开共享内存,映射它并且检查数据是不是被正确的初始化了。
- #include <boost/interprocess/shared_memory_object.hpp>
- #include <boost/interprocess/mapped_region.hpp>
- #include <cstring>
- #include <cstdlib>
- #include <string>
- int main(int argc, char *argv[])
- {
- using namespace boost::interprocess;
- if(argc == 1){ //Parent process
- //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 shared memory object.
- shared_memory_object shm (create_only, "MySharedMemory", read_write);
- //Set size
- shm.truncate(1000);
- //Map the whole shared memory in this process
- mapped_region region(shm, read_write);
- //Write all the memory to 1
- std::memset(region.get_address(), 1, region.get_size());
- //Launch child process
- std::string s(argv[0]); s += " child ";
- if(0 != std::system(s.c_str()))
- return 1;
- }
- else{
- //Open already created shared memory object.
- shared_memory_object shm (open_only, "MySharedMemory", read_only);
- //Map the whole shared memory in this process
- mapped_region region(shm, read_only);
- //Check that memory was initialized to 1
- char *mem = static_cast<char*>(region.get_address());
- for(std::size_t i = 0; i < region.get_size(); ++i)
- if(*mem++ != 1)
- return 1; //Error checking memory
- }
- return 0;
- }
对没有共享内存对象的系统进行模拟
Boost.Interprocess在POSIX语义环境下提供了可移植的共享内存。一些操作系统不支持POSIX形式定义的共享内存:
- Windows操作系统提供了使用分页文件支持内存的共享内存,但是生命周期的意义与POSIX定义得不同(更多详情,参考原生Windows共享内存章节)。
- 一些UNIX系统不能完全支持POSIX共享内存对象。
在这些平台上,共享内存采用映射文件来模拟。这些映射文件创建在临时文件夹下的"boost_interprocess"文件夹中。在Windows平台下,如果"Common AppData" 关键字出现在注册表中,"boost_interprocess" 文件夹就创建在那个文件夹下(XP系统通常是"C:\Documentsand Settings\All Users\Application Data" ,Vista则是"C:\ProgramData")。对没有注册表项的Windows平台或是Unix系统,共享内存被创建在系统临时文件夹下("/tmp"或类似)。
由于采用了这种模拟方式,共享内存在部分这些操作系统中具有文件系统生命周期。
删除共享内存
shared_memory_object提供了一个静态删除函数用于删除一个共享内存对象。
如果共享内存对象不存在或是被另一个进程打开,则函数调用会失败。需要注意的是这个函数与标准的C函数int remove(constchar *path)类似。在UNIX系统中,shared_memory_object::remove调用shm_unlink:
该函数将删除名称所指出的字符串命名的共享内存对象名称。
- 当断开连接时,存在一个或多个对此共享内存对象的引用,则在函数返回前,名称会鲜卑删除,但是内存对象内容的删除会延迟至所有对共享内存对象的打开或映射的引用被删除后进行。
- 即使对象在最后一个函数调用后继续存在,复用此名字将导致创建一个 boost::interprocess::shared_memory_object实例,就好像采用此名称的共享内存对象不存在一样(也即,尝试打开以此名字命名的对象会失败,并且一个采用此名字的新对象会被创建)。
在Windows操作系统中,当前版本支持对UNIX断开行为通常可接受的仿真:文件会用一个随机名字重命名,并被标记以便最后一个打开的句柄关闭时删除它。
转载自:https://www.cnblogs.com/yeqing-vzenith/p/5175537.html