如何在C++14中将多字节值写入共享内存



假设我有两个进程,它们都使用shm_openmmap共享一个内存块,并且存在一个共享的同步原语——比如说信号量——确保对内存的独占访问。即没有比赛条件。

我的理解是,从mmap返回的指针仍然必须标记为volatile,以防止缓存读取。

现在,如何将例如std::uint64_t写入存储器中的任何对齐位置?

当然,我会简单地使用std::memcpy,但它不适用于指向易失性内存的指针。

第一次尝试

// Pointer to the shared memory, assume it is aligned correctly.
volatile unsigned char* ptr;
// Value to store, initialize "randomly" to prevent compiler
// optimization, for testing purposes.
std::uint64_t value = *reinterpret_cast<volatile std::uint64_t*>(nullptr);
// Store byte-by-byte
unsigned char* src = reinterpret_cast<unsigned char*>(&value);
for(std::size_t i=0;i<sizeof(value);++i)
ptr[i]=src[i];

Godbolt。

我坚信这个解决方案是正确的,但即使使用-O3,也有8个1字节的传输。这确实不是最佳选择。

第二次尝试

既然我知道在我锁定内存的时候没有人会改变内存,也许volatile毕竟是不必要的?

// Pointer to the shared memory, assume it is aligned correctly.
volatile unsigned char* ptr;
// Value to store, initialize "randomly" to prevent compiler
// optimization for testing purposes.
std::uint64_t value = *reinterpret_cast<volatile std::uint64_t*>(0xAA);
unsigned char* src = reinterpret_cast<unsigned char*>(&value);
//Obscure enough?
auto* real_ptr = reinterpret_cast<unsigned char*>(reinterpret_cast<std::uintptr_t>(ptr));
std::memcpy(real_ptr,src,sizeof(value));

Godbolt。

但这似乎不起作用,编译器看穿了演员阵容,什么也不做。Clang生成ud2指令,不知道为什么,我的代码中有UB吗?除了value初始化。

第三次尝试

这个来自于这个答案。但我认为它确实打破了严格的混叠规则,不是吗?

// Pointer to the shared memory, assume it is aligned correctly.
volatile unsigned char* ptr;
// Value to store, initialize "randomly" to prevent compiler
// optimization for testing purposes.
std::uint64_t value = *reinterpret_cast<volatile std::uint64_t*>(0xAA);
unsigned char* src = reinterpret_cast<unsigned char*>(&value);
volatile std::uint64_t* dest = reinterpret_cast<volatile std::uint64_t*>(ptr);
*dest=value;

Godbolt。

Gcc实际上做了我想要的事情——一个简单的一条指令来复制64位的值。但如果是UB,那就没用了。

修复它的一种方法是在那个地方真正创建std::uint64_t对象。但是,显然placementnew也不适用于volatile指针。

问题

  • 那么,有比逐字节复制更好(安全)的方法吗
  • 我还想复制更大的原始字节块。这能比单个字节做得更好吗
  • 有没有可能强迫memcpy做正确的事情
  • 我是否不必要地担心性能,而应该继续循环
  • 任何例子(大部分是C)根本不使用volatile,我也应该这样做吗?mmaped指针是否已被区别对待?如何

感谢您的建议。

编辑:

两个进程都在同一个系统上运行。另外,请假设这些值可以逐字节复制,而不是说复杂的虚拟类存储指向某个地方的指针。所有整数和没有浮动将是好的。

我的理解是,从mmap返回的指针仍然必须标记为volatile,以防止缓存读取。

你的理解是错误的。不要使用volatile来控制内存可见性——这不是它的用途。它要么不必要地昂贵,要么不够严格,要么两者兼而有之。

例如,考虑一下GCC关于volatile的文档,其中写道:

对非易失性对象的访问不按易失性访问排序。您不能使用易失性对象作为内存屏障来命令对非易失性内存的一系列写入

如果您只是想避免撕裂、缓存和重新排序,请改用<atomic>。例如,如果您有一个现有的共享uint64_t(并且它已正确对齐),只需通过std::atomic_ref<uint64_t>访问它。您可以将获取、发布或CAS直接用于此。

如果您需要正常的同步,那么您现有的信号量就可以了。如下所示,它已经提供了任何必要的围栏,并防止在等待/后呼叫中重新排序。它不会阻止它们之间的重新排序或其他优化,但这通常是可以的。


对于

任何示例(主要是C)都不使用volatile,我也应该这样做吗?mmaped指针已经被区别对待了吗?怎样

答案是,无论使用什么同步,都需要应用适当的围栏。

POSIX将这些函数列为";同步存储器";,这意味着它们必须同时发出任何所需的内存栅栏,并防止不适当的编译器重新排序。因此,例如,您的实现必须避免在pthread_mutex_*lock()sem_wait()/sem_post()调用之间移动内存访问,以便符合POSIX,即使在其他情况下它是合法的C或C++。

当您使用C++的内置线程或原子支持时,正确的语义是语言标准的一部分,而不是平台扩展(但共享内存不是)。

假设我有两个进程,它们都使用shm_open和mmap共享一个内存块,并且存在一个共享的同步原语-比如说信号量-确保对内存的独占访问。即没有比赛条件。

您需要的不仅仅是对内存的独占访问。你需要同步内存。我见过的每一个信号灯都已经做到了。如果你的没有,那就是错误的同步原语。切换到另一个。

我的理解是,从mmap返回的指针仍然必须标记为volatile,以防止缓存读取。

volatile不会阻止缓存读取,但几乎所有信号量、互斥体和其他同步原语都会阻止缓存读取和跨它们写入。否则,它们几乎不可能使用。

你在用什么信号量?如果它不同步内存,那么它就是不适合这个工作的工具。

相关内容

  • 没有找到相关文章

最新更新