在Linux/x86上mmapping/dev/shmWrite-Back(WB)或者Non-CacheableWrite-Combining(WC)返回的内存是多少?
Is the memory returned from mmapping /dev/shm Write-Back (WB) or Non-Cacheable Write-Combining (WC) on Linux/x86?
我有两个 C++ 进程通过 memory-mapped Single-Producer Single-Consumer (SPSC) double buffer 进行通信。这些进程只会在 Linux/Intel x86-64 上 运行。语义是生产者填充前台缓冲区,然后交换指针并更新计数器,让消费者知道它可以 memcpy()
后台缓冲区。所有共享状态都存储在映射区域开始处的 header 块中。
int _fd;
volatile char *_mappedBuffer;
...
_fd = shm_open("/dev/shm/ipc_buffer", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
...
_mappedBuffer = static_cast<char *>(mmap(nullptr, _shmFileSizeBytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE | MAP_POPULATE, _fd, 0));
生产者需要一个 StoreStore
屏障来确保交换在计数器递增之前可见,这在 x86 上应该是隐式的,with Write-Back (WB) memory:
void produce() {
...
// swap pointers
char *tmp = _frontBuffer;
_frontBuffer= _backBuffer;
_backBuffer= tmp;
...
// SFENCE needed here? Yes if uncacheable WC, NO if WB due to x86 ordering guarantees?
asm volatile ("sfence" ::: "memory");
_flipCounter++;
}
消费者需要一个 LoadLoad
屏障 if (WC) 以确保它在新的后台缓冲区指针之前加载翻转计数器。如果内存是 (WB),那么我们知道 CPU 不能 re-order 负载:
bool consume(uint64_t &localFlipVer, char *dst) {
if (localFlipVer < _flipCounter) {
// LFENCE needed here? Yes if uncacheable WC, NO if WB due to x86 ordering guarantees?
asm volatile ("lfence" ::: "memory");
std::memcpy(dst, _backBuffer, _bufferSize);
localFlipVer++;
return true;
}
return false;
}
我的问题和我的假设:
mmmap/dev/shm
Write-Back还是Non-cacheableWrite-Combining返回的memory-mapped区域?如果是后者,根据
,存储和加载是弱排序的并且不遵循传统的 x86 排序保证(没有 StoreStore 或 LoadLoad re-orderings)
https://hadibrais.wordpress.com/2019/02/26/the-significance-of-the-x86-sfence-instruction/
https://preshing.com/20120913/acquire-and-release-semantics/#IDComment721195741
因此,我必须使用 SFENCE
和 LFENCE
,而通常情况下(使用 WB),我可以只用一个编译器障碍 asm volatile ("" ::: "memory");
/dev/shm/
只是一个 tmpfs
挂载点,就像 /tmp
.
记忆你mmap
在文件中有正常的WB可缓存,就像MAP_ANONYMOUS
。它遵循正常的 x86 内存排序规则(程序顺序 + 带存储转发的存储缓冲区),因此您不需要 SFENCE 或 LFENCE,只阻止 acq_rel 排序的编译时重新排序。或者对于seq_cst,MFENCE或者锁定操作,比如使用xchg
来存储。
对于 lock_free
类型,您可以在指向 SHM 的指针上使用 C11 <stdatomic.h>
函数。 (通常是指针宽度的任何 2 的幂大小。)
非无锁对象在执行操作的进程的地址-space中使用锁的散列table,因此不同的进程不会尊重彼此的锁。 16 字节对象仍然可以使用 lock cmpxchg16b
,它是无地址的并且可以跨进程工作,即使 GCC7 和更高版本将其报告为非无锁 for reasons 即使您使用 -mcx16
进行编译.
我认为在主流 Linux 内核中没有办法为用户 space 分配 WB 以外的任何类型的内存。 (X 服务器或直接渲染客户端映射视频 RAM 除外;我的意思是无法映射具有不同 PAT 内存类型的普通 DRAM 页面。)另请参阅
对于不尝试将存储批量存储到一个宽 SIMD 存储中的普通代码,除 WB 之外的任何类型都将是潜在的性能灾难。例如如果您在 SHM 中有一个受共享互斥锁保护的数据结构,如果关键部分内的正常访问不可缓存,那将很糟糕。特别是在同一个线程重复获取同一个锁和reading/writing同一个数据的无竞争情况下。
所以它总是 WB 是有充分理由的。
我有两个 C++ 进程通过 memory-mapped Single-Producer Single-Consumer (SPSC) double buffer 进行通信。这些进程只会在 Linux/Intel x86-64 上 运行。语义是生产者填充前台缓冲区,然后交换指针并更新计数器,让消费者知道它可以 memcpy()
后台缓冲区。所有共享状态都存储在映射区域开始处的 header 块中。
int _fd;
volatile char *_mappedBuffer;
...
_fd = shm_open("/dev/shm/ipc_buffer", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
...
_mappedBuffer = static_cast<char *>(mmap(nullptr, _shmFileSizeBytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE | MAP_POPULATE, _fd, 0));
生产者需要一个 StoreStore
屏障来确保交换在计数器递增之前可见,这在 x86 上应该是隐式的,with Write-Back (WB) memory:
void produce() {
...
// swap pointers
char *tmp = _frontBuffer;
_frontBuffer= _backBuffer;
_backBuffer= tmp;
...
// SFENCE needed here? Yes if uncacheable WC, NO if WB due to x86 ordering guarantees?
asm volatile ("sfence" ::: "memory");
_flipCounter++;
}
消费者需要一个 LoadLoad
屏障 if (WC) 以确保它在新的后台缓冲区指针之前加载翻转计数器。如果内存是 (WB),那么我们知道 CPU 不能 re-order 负载:
bool consume(uint64_t &localFlipVer, char *dst) {
if (localFlipVer < _flipCounter) {
// LFENCE needed here? Yes if uncacheable WC, NO if WB due to x86 ordering guarantees?
asm volatile ("lfence" ::: "memory");
std::memcpy(dst, _backBuffer, _bufferSize);
localFlipVer++;
return true;
}
return false;
}
我的问题和我的假设:
mmmap/dev/shm
Write-Back还是Non-cacheableWrite-Combining返回的memory-mapped区域?如果是后者,根据
https://hadibrais.wordpress.com/2019/02/26/the-significance-of-the-x86-sfence-instruction/
https://preshing.com/20120913/acquire-and-release-semantics/#IDComment721195741
因此,我必须使用 SFENCE
和 LFENCE
,而通常情况下(使用 WB),我可以只用一个编译器障碍 asm volatile ("" ::: "memory");
/dev/shm/
只是一个 tmpfs
挂载点,就像 /tmp
.
记忆你mmap
在文件中有正常的WB可缓存,就像MAP_ANONYMOUS
。它遵循正常的 x86 内存排序规则(程序顺序 + 带存储转发的存储缓冲区),因此您不需要 SFENCE 或 LFENCE,只阻止 acq_rel 排序的编译时重新排序。或者对于seq_cst,MFENCE或者锁定操作,比如使用xchg
来存储。
对于 lock_free
类型,您可以在指向 SHM 的指针上使用 C11 <stdatomic.h>
函数。 (通常是指针宽度的任何 2 的幂大小。)
非无锁对象在执行操作的进程的地址-space中使用锁的散列table,因此不同的进程不会尊重彼此的锁。 16 字节对象仍然可以使用 lock cmpxchg16b
,它是无地址的并且可以跨进程工作,即使 GCC7 和更高版本将其报告为非无锁 for reasons 即使您使用 -mcx16
进行编译.
我认为在主流 Linux 内核中没有办法为用户 space 分配 WB 以外的任何类型的内存。 (X 服务器或直接渲染客户端映射视频 RAM 除外;我的意思是无法映射具有不同 PAT 内存类型的普通 DRAM 页面。)另请参阅
对于不尝试将存储批量存储到一个宽 SIMD 存储中的普通代码,除 WB 之外的任何类型都将是潜在的性能灾难。例如如果您在 SHM 中有一个受共享互斥锁保护的数据结构,如果关键部分内的正常访问不可缓存,那将很糟糕。特别是在同一个线程重复获取同一个锁和reading/writing同一个数据的无竞争情况下。
所以它总是 WB 是有充分理由的。