8 字节 memcpy() 在 64 位 Linux 机器上是原子的吗?

问题描述 投票:0回答:1

我在 PHP 中使用 8 字节共享内存段,使用 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 linux concurrency atomic memcpy
1个回答
0
投票

如果您正在谈论的

memcpy
是C函数,则glibc的x86-64汇编
memcpy
对于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
,只有第二个读数很重要;第一个的结果被覆盖,因此它实际上就像 x86-64 上的 C++ std::atomic
.load(acquire)

这是一个没有任何保证的实现细节。 不过,这个选择是有道理的:如果他们使用的 size=5-8 范围,对于两个 4 字节副本部分重叠或根本不重叠,则使用一个 8 字节副本重新加载结果将导致存储转发停顿,因为它与两个单独的存储重叠。


我不知道 MUSL 做什么,或者其他 C 库或其他架构。

其他具有高效未对齐加载/存储的架构(如 AArch64)可能会对小于 2 倍整数寄存器的大小执行类似的操作。 但这并不能保证。 如果它确实使用这种策略,就会像

.load(relaxed)
因为 AArch64 是弱有序的。

这当然假设共享内存足够对齐,以便普通加载/存储在目标计算机上是原子的。 (您复制的局部变量并不重要,因为它不是共享的。因此,正如 Barmar 在评论中指出的那样,

"abcdefgh"
可能未对齐,但这并不重要,因为您不需要原子访问到它,仅用于在寄存器到达后存储这 8 个字节。)

在 Intel CPU 上,这意味着 8 字节共享内存不得跨越缓存行边界,但可以在 64 字节块内的任何位置错位。 在 AMD 上,它必须是 8 字节对齐。 (在较新的 AMD CPU 上,在 16 或 32 甚至 64 字节块内未对齐仍会提供原子加载/存储。)请参阅 为什么自然对齐变量上的整数赋值在 x86 上是原子的?

在其他架构上,您通常需要自然对齐来保证原子性,所以这是您最好的选择。

© www.soinside.com 2019 - 2024. All rights reserved.