Ubuntu交叉编译树莓派Zero2 W项目中,如何初始化共享内存内的std::recursive_mutex及设备数据结构
这个问题我之前做跨进程共享内存场景时也碰到过!核心原因是std::recursive_mutex是不可拷贝、不可赋值的类型,你直接写device_data_->lock_ = {}属于赋值操作,而它的赋值运算符已经被标准库显式删除了,所以编译器会抛出你看到的错误。要解决这个问题,你不能先把共享内存mmap成结构体指针再逐个赋值成员,而是要在共享内存的原始地址上原地构造整个Lps22DeviceData对象,这样才能正确初始化所有非POD成员(包括互斥锁、chrono时间点这类需要构造函数的类型)。
具体解决方案:用Placement New原地构造对象
你的代码需要做以下调整:
- 先完成
shm_open和ftruncate的步骤,保持不变; - mmap获取共享内存的原始地址(先存为
void*); - 用
placement new在这个地址上直接构造Lps22DeviceData实例,这样会自动调用所有成员的构造函数,包括std::recursive_mutex的默认构造; - 之后再初始化其他数据成员。
修改后的代码示例:
int shmem_fd = shm_open(shmem_fname.c_str(), O_RDWR|O_CREAT|O_EXCL, 0666); if (shmem_fd != -1) { // 调整共享内存大小 int result = ftruncate(shmem_fd, sizeof(Lps22DeviceData)); if (result == -1) { // 处理ftruncate错误:比如close fd、shm_unlink close(shmem_fd); shm_unlink(shmem_fname.c_str()); // 这里可以加错误日志或抛出异常 return; } // 先mmap获取共享内存的原始地址 void* raw_shmem = mmap(NULL, sizeof(Lps22DeviceData), PROT_READ | PROT_WRITE, MAP_SHARED, shmem_fd, 0); if (raw_shmem == MAP_FAILED) { close(shmem_fd); shm_unlink(shmem_fname.c_str()); // 处理mmap错误 return; } // 用placement new在共享内存地址上原地构造Lps22DeviceData对象 device_data_ = new (raw_shmem) Lps22DeviceData(); // 现在可以安全地初始化其他数据成员了 device_data_->read_total_ = 0; device_data_->initialized = false; device_data_->temperature_measurement_ = 0; device_data_->temperature_measurement_system_time_ = system_clock::now(); device_data_->temperature_measurement_steady_time_ = steady_clock::now(); device_data_->temperature_response_time = milliseconds(0); device_data_->pressure_measurement_ = 0; device_data_->pressure_measurement_system_time_ = system_clock::now(); device_data_->pressure_measurement_steady_time_ = steady_clock::now(); device_data_->pressure_response_time = milliseconds(0); device_data_->initialized = true; }
关键细节解释
为什么Placement New能解决问题?
Placement New的作用是在你指定的内存地址上直接调用对象的构造函数,而不是分配新内存。对于Lps22DeviceData里的std::recursive_mutex,它会调用默认构造函数初始化一个可用的互斥锁——在Linux平台上,std::recursive_mutex底层是基于pthread_mutex_t实现的,只要在共享内存里正确初始化,跨进程是可以正常使用的。其他进程如何访问?
其他进程打开这个共享内存时,不能再调用Placement New(否则会重复构造,破坏已经初始化的对象),只需要mmap获取地址,然后直接通过Lps22DeviceData*指针访问即可,通过检查initialized标志判断是否已经完成初始化。额外注意事项
- 确保所有进程使用相同版本的C++标准库:因为不同版本的
std::recursive_mutex或std::chrono实现可能有内存布局差异,跨进程使用会导致异常。 - 最后一个退出的进程需要手动析构对象:当确认没有其他进程再使用共享内存时,调用
device_data_->~Lps22DeviceData()析构对象,之后再执行munmap、close、shm_unlink操作。 - 结构体布局一致性:编译所有进程时,要保证编译器的对齐选项、C++标准等完全一致,避免结构体内存布局不匹配导致的访问错误。
- 确保所有进程使用相同版本的C++标准库:因为不同版本的
可选优化:给类添加显式构造函数
你可以给Lps22DeviceData添加一个默认构造函数,把成员初始化逻辑都放在里面,这样代码更整洁,也避免手动赋值时的遗漏:
#include <chrono> using namespace std::chrono; class Lps22DeviceData { public: std::recursive_mutex lock_; uint64_t read_total_; bool initialized; int16_t temperature_measurement_; time_point<system_clock> temperature_measurement_system_time_; time_point<steady_clock> temperature_measurement_steady_time_; milliseconds temperature_response_time; int32_t pressure_measurement_; time_point<system_clock> pressure_measurement_system_time_; time_point<steady_clock> pressure_measurement_steady_time_; milliseconds pressure_response_time; // 显式默认构造函数,统一初始化所有成员 Lps22DeviceData() : lock_(), read_total_(0), initialized(false), temperature_measurement_(0), temperature_measurement_system_time_(system_clock::now()), temperature_measurement_steady_time_(steady_clock::now()), temperature_response_time(milliseconds(0)), pressure_measurement_(0), pressure_measurement_system_time_(system_clock::now()), pressure_measurement_steady_time_(steady_clock::now()), pressure_response_time(milliseconds(0)) {} };
这样用Placement New构造时,所有成员都会自动完成初始化,你甚至可以省略后面的手动赋值步骤,直接把initialized设为true即可。
备注:内容来源于stack exchange,提问作者chriskot

