Безопасно ли выделение массивов атомов с помощью системных вызовов виртуальной памяти?

Я разрабатываю базу данных в памяти, и моей системе требуется большой массив std::atomic_int объекты, которые примерно действуют как блокировки для записей базы данных. Теперь я бы предпочел распределить эти блокировки с помощью системных вызовов VM, таких как mmap в Unix-подобных системах и VirtualAlloc на Win32 / 64. Для этого есть несколько причин, и только одна из них не нуждается в явной инициализации памяти (т. Е. Память, выделенная системными вызовами ВМ, гарантированно обнуляется ОС). Итак, я хотел бы сделать это:

#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 гарантированно возвращает память, выровненную по границе страницы виртуальной машины, поэтому любые требования выравнивания std::atomic_int должен быть удостоен чести, и конструктор std::atomic_int не инициализирует значение, поэтому нет опасности не вызвать конструктор, так как длинные операции чтения и записи реализуются разумным способом (например, с использованием __atomic_* Встроенные GCC и лязг).

Тем не менее, я могу себе представить, что этот код не обязательно безопасен в соответствии со стандартом C ++ — правильно ли я так думаю? Если это правильно, есть ли какая-либо проверка, чтобы, если код успешно компилируется на целевой платформе (то есть, если реализация std::atomic_int это то, что я ожидаю, что это будет), тогда все работает, как я ожидаю?

В связи с этим, я ожидаю следующий код, где 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);

Причина, по которой я думаю, что это не должно работать, заключается в том, что разумная реализация std::atomic_int::store с std::memory_order_relaxed на x86 это просто нормальный ход, который гарантированно будет атомарным только для доступа с выравниванием по словам. Если я прав в этом, могу ли я что-нибудь добавить в код, чтобы защитить от таких ситуаций и, возможно, обнаружить такие проблемы во время компиляции?

0

Решение

Это безопасно как mmap выделяет память, соответствующим образом выровненную для любого встроенного и SIMD-типа.

Убедитесь, что вы звоните std::uninitialized_default_construct_n (или ваш собственный эквивалент до C ++ 17), чтобы удовлетворить требования стандарта C ++, что конструктор должен быть вызван, и std::destroy_n вызывать деструкторы после использования. Эти вызовы компилируются в 0 инструкций, потому что конструктор по умолчанию и деструктор std::atomic<> являются тривиальный (ничего не делать):

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);
0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]