boost库本身不是系统基本的api,主要的贡献是封装原有的系统基本接口。linux下面的共享内存使用很繁琐,推荐使用boost库里面的共享内存接口,底层是POSIX的共享内存。
一、常用接口
常用接口是以下几个:
1、shared_memory_object 类
shared_memory_object shdmem{open_or_create, "Boost", read_write};
这个类用来创建共享内存标识,这个类创建好之后,/dev/shm下面会产生一个文件,文件名就是共享内存名。下面介绍构造函数的三个参数:
(1)参数1:表示共享内存标识的创建方式。
open_or_create:表示如果共享内存标识存在,那么直接打开;如果共享内存标识不存在,那么直接新建。
open_only:如果共享内存存在,那么直接打开;如果共享内存不存在,直接core dump!
create_only:如果共享内存不存在,那么直接打开;如果共享内存存在,直接core dump!
所以,如果最好用open_or_create创建。
(2)参数2:共享内存名。会在/dev/shm下面创建同名文件。
(3)读写模式,这个一般设置成read_write。
2、truncate函数
truncate(size_t size)是shared_memory_object 的普通成员函数,表示要创建多少字节的共享内存。
3、mapped_region类
mapped_region region{shdmem, read_write};
mapped_region类用于进程映射共享内存,构造函数里面有两个参数,第一个参数是shared_memory_object 对象,第二个参数是读写方式。
之后调用region.get_address()可以返回共享内存地址。
4、remove函数
这个函数用于删除共享内存,如果没有进程调用这个函数,那么共享内存无法删除。
如果删除多了其实没有什么问题,这个函数带返回值,表示删除成功与否,无视返回值就好了。
二、使用示例
进程A,先启动
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <iostream>
#include <unistd.h>
using namespace boost::interprocess;
int main()
{
shared_memory_object shdmem{open_or_create, "Boost", read_write};
shdmem.truncate(1024);
mapped_region region{shdmem, read_write};
int *i1 = static_cast<int*>(region.get_address());
*i1 = 99;
sleep(30);
bool removed = shared_memory_object::remove("Boost");
std::cout<<removed<<std::endl;
}
进程B,后启动
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <iostream>
#include <unistd.h>
using namespace boost::interprocess;
int main()
{
shared_memory_object shdmem{open_or_create, "Boost", read_write};
shdmem.truncate(2048);
mapped_region region2{shdmem, read_only};
int *i2 = static_cast<int*>(region2.get_address());
std::cout << *i2 << '\n';
bool removed = shared_memory_object::remove("Boost");
std::cout<<removed<<std::endl;
}
我故意把两个进程映射的内存大小设置成不一样的,这是无伤大雅的。结果就是只有前1024个字节可以用于进程通信。
进程A输出:
0
他删除共享内存失败了,因为已经被进程B删除了,但是正常退出,没有崩溃。
进程B输出:
99
1
正常获取到了数据,而且正常删除了共享内存。
三、不手动删除共享内存
先执行进程A
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <iostream>
#include <unistd.h>
using namespace boost::interprocess;
int main()
{
shared_memory_object shdmem{open_or_create, "Boost", read_write};
shdmem.truncate(1024);
mapped_region region{shdmem, read_write};
int *i1 = static_cast<int*>(region.get_address());
*i1 = 99;
}
没有删除共享内存。可以看到/dev/shm/Boost一直存在。
进程A退出后执行进程B:
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <iostream>
#include <unistd.h>
using namespace boost::interprocess;
int main()
{
shared_memory_object shdmem{open_or_create, "Boost", read_write};
shdmem.truncate(2048);
mapped_region region2{shdmem, read_only};
int *i2 = static_cast<int*>(region2.get_address());
std::cout << *i2 << '\n';
bool removed = shared_memory_object::remove("Boost");
std::cout<<removed<<std::endl;
}
可以输出99,说明读取了残留数据。但是也说明打开存在的共享内存不会报错。
四、如何保证共享内存被回收?
这在linux下面其实很难办,因为linux下面的共享内存还是封装的POSIX的shmget函数,linux共享内存没有windows的功能那么强大。
好在,linux下boost共享内存对多次删除操作不敏感,只要进程是正常退出的,共享内存都有办法清理。
唯一的问题是,异常退出该怎么办?
其实可以借助linux的信号注册机制处理这个问题。
简单地说,就是我们向系统注册一个函数,进程收到某个信号后,会触发这个函数,这个函数可以是段错误、零除错误、ctrl+c退出信号,等等。
可以在注册函数里面,加上对共享内存的清除操作,如果进程发生了core dump或者被人为终止,都可以清理共享内存。代码示例如下:
#include <boost/interprocess/shared_memory_object.hpp>
#include <cstdlib>
#include <iostream>
#include <csignal>
namespace bip = boost::interprocess;
void cleanup_shm(int signal) {
bip::shared_memory_object::remove("MyShm");
std::cout << "Cleanup: Shared memory removed." << std::endl;
std::exit(EXIT_SUCCESS);
}
int main() {
// 注册信号处理函数
std::signal(SIGFPE, cleanup_shm); // 零除错误
std::signal(SIGINT, cleanup_shm); // Ctrl+C
std::signal(SIGSEGV, cleanup_shm); // 段错误
// 创建共享内存
bip::shared_memory_object shm(bip::open_or_create, "MyShm", bip::read_write);
shm.truncate(1024);
// ... 使用共享内存 ...
int * p = NULL;
*p = 1;
//或者是:
//int a = 1/0;
//或者是:sleep过程中ctrl+c退出
return 0;
}
经过测试,即使异常退出也可以正常删除共享内存。
五、待讨论点
(1)信号注册的方法,对kill -9无效。
(2)对于boost的共享内存,官方提供的最新版是managed_shared_memory ,但是这个接口感觉太不灵活了,就没讨论。