Является ли 8-байтовый memcpy() атомарным на 64-битной машине с Linux?

Я использую 8-байтовый сегмент общей памяти в PHP, используя функции shmop_*. Глядя на исходный код PHP, я вижу, что функции shmop_write() и shmop_read() используют memcpy() для заполнения/чтения этих 8 байт.

Интересно, достаточно ли на 64-битной Linux-машине memcpy() умен, чтобы скопировать все двойное слово за один раз (одну инструкцию), что делает операции чтения и записи эффективными атомарными.

Я не уверен, что эти сегменты общей памяти всегда выровнены по 64-битному принципу.

В качестве примера:

$shmop = shmop_open(ftok(__FILE__, 'R'), 'c', 0644, 8);

shmop_write($shmop, "abcdefgh", 0); // <= is this operation atomic
$a = shmop_read($shmop, 0, 8); // <= is this operation atomic

🤔 А знаете ли вы, что...
PHP широко используется для разработки систем управления контентом, таких как WordPress и Joomla.


50
1

Ответ:

Решено

Если memcpy, о котором вы говорите, это функция C, сборка glibc x86-64 memcpy for size=8 разветвляется на случай 8-15, который создает две полностью перекрывающиеся 8-байтовые копии. (https://codebrowser.dev/glibc/glibc/sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S.html#308).

Для больших размеров до 15 они будут перекрываться посередине на меньшее количество байтов. Так что shmop_write могло случиться как до, так и после очередного магазина. Для shmop_read имеет значение только второе прочтение; результат первого перезаписывается, поэтому он фактически похож на C++ std::atomic .load(acquire) на x86-64.

Это деталь реализации, которая ничем не гарантирована. Однако выбор имеет смысл: если бы они использовали диапазон size=5-8, который либо частично перекрывался, либо не перекрывался вообще для двух 4-байтовых копий, 8-байтовая перезагрузка результата привела бы к остановке пересылки в хранилище, поскольку он перекрывается. два отдельных магазина.


Я не знаю, что делает MUSL, другие библиотеки C или другие архитектуры.

Другие архитектуры с эффективными невыровненными загрузками/сохранениями (например, AArch64) могут делать что-то подобное для размеров, меньших 2x целочисленного регистра. Но это не гарантировано. Если бы он использовал эту стратегию, это было бы похоже на .load(relaxed), поскольку AArch64 слабо упорядочен.

Это, конечно, предполагает, что общая память достаточно выровнена, чтобы простые загрузки/сохранения были атомарными на целевой машине. (Локальная переменная, из/в которую вы копируете, не имеет значения, поскольку она не является общей. Итак, как отметил Бармар в комментариях, "abcdefgh" может быть не выровнена, но это не имеет значения, потому что вам не нужен атомарный доступ к ней. , только для хранения этих 8 байт из регистра, как только они туда попадут.)

В процессорах Intel это означает, что 8 байт общей памяти не должны охватывать границу строки кэша, но могут быть смещены в любом месте внутри 64-байтового блока. В AMD он должен быть выровнен по 8 байтам. (На более поздних процессорах AMD смещенное выравнивание в пределах 16-, 32- или даже 64-байтового блока по-прежнему будет давать атомарные загрузки/сохранения.) См. Почему целочисленное присваивание естественно выровненной переменной является атомарным на x86?

В других архитектурах для обеспечения гарантий атомарности часто требуется естественное выравнивание, так что это лучший вариант.