使用虚拟内存系统调用分配原子数组是否安全?

时间:2018-12-04 14:22:15

标签: c++ atomic mmap virtualalloc

我正在开发内存数据库,我的系统需要一大堆std::atomic_int对象,这些对象大致上充当数据库记录的锁。现在,我希望使用VM系统调用来分配这些锁,例如在类Unix系统上使用mmap,在Win32 / 64上使用VirtualAlloc。造成这种情况的原因有很多,其中只有一种原因不必显式初始化内存(即,由VM系统调用分配的内存可确保由OS清零)。因此,我基本上想这样做:

#include <sys/mman.h>
#include <atomic>

// ...
size_t numberOfLocks = ... some large number ...;
std::atomic_int* locks = reinterpret_cast<std::atomic_int*>(mmap(0, numberOfLocks * sizeof(std::atomic_int), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0));
// ... use locks[i].load() or locks[i].store() as with any memory order as appropriate

我的主要问题是此代码是否安全。我直觉地期望代码可以在任何具有现代编译器的合理平台上运行:mmap保证返回与VM页面边界对齐的内存,因此应遵守std::atomic_int的所有对齐要求,并且std::atomic_int的构造函数不会初始化该值,因此,只要以合理的方式(例如,使用GCC和clang的__atomic_*内置函数)实现长时间的读写,就不会存在不调用构造函数的危险。

但是,我可以想像按照C ++标准,此代码不一定是安全的-我是这样认为的吗?如果是正确的话,是否有任何检查,以便如果代码在目标平台上成功编译(即,如果std::atomic_int的实现符合我的期望),那么一切都会按我的预期进行? / p>

与此相关,我希望以下代码(其中std::atomic_int不符合属性)在x86上中断:

uint8_t* region = reinterpret_cast<uint8_t*>(mmap(...));
std::atomic_int* lock = reinterpret_cast<std::atomic_int*>(region + 1);
lock->store(42, std::memory_order_relaxed);

之所以我认为这不起作用,是因为在x86上合理地实现std::atomic_int::storestd::memory_order_relaxed只是正常的做法,这保证仅对于单词对齐的访问才是原子的。如果我对此表示正确,那么我可以在代码中添加任何内容来防范这种情况并在编译时检测到此类问题吗?

1 个答案:

答案 0 :(得分:0)

这很安全,因为mmap会为任何内置和SIMD类型分配适当对齐的内存。

请确保您调用std::uninitialized_default_construct_n(或您自己的C ++ 17之前的版本)以满足C ++标准(必须调用构造函数)的要求,以及std::destroy_n在使用后调用析构函数。这些调用编译为0条指令,因为std::atomic<>的默认构造函数和析构函数为trivial (do nothing)

size_t numberOfLocks = ... some large number ...;
auto* locks = static_cast<std::atomic_int*>(mmap(0, numberOfLocks * sizeof(std::atomic_int), ...));

// initialize
std::uninitialized_default_construct_n(locks, numberOfLocks); 
// ... use ...
// uninitialize
std::destroy_n(locks, numberOfLocks);