为什么QEMU将__atomic_thread_fence()与barrier()一起使用?

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

QEMU atomic.h 具有以下定义:

#define smp_mb()                     ({ barrier(); __atomic_thread_fence(__ATOMIC_SEQ_CST); })
#define smp_mb_release()             ({ barrier(); __atomic_thread_fence(__ATOMIC_RELEASE); })
#define smp_mb_acquire()             ({ barrier(); __atomic_thread_fence(__ATOMIC_ACQUIRE); })

它有注释解释了为什么编译器障碍

barrier()
是必要的:

/* Manual memory barriers
 *
 *__atomic_thread_fence does not include a compiler barrier; instead,
 * the barrier is part of __atomic_load/__atomic_store's "volatile-like"
 * semantics. If smp_wmb() is a no-op, absence of the barrier means that
 * the compiler is free to reorder stores on each side of the barrier.
 * Add one here, and similarly in smp_rmb() and smp_read_barrier_depends().
 */

我以前没有使用过

__atomic_thread_fence
,但我在网上的搜索表明
__atomic_thread_fence
可以防止编译器和CPU重新排序内存访问。例如,它的参考页面herehere并没有说这只是CPU障碍。 here 的答案明确指出它既是编译器障碍又是 CPU 障碍。

这是否意味着这些定义中的

barrier()
是多余的? (我只是好奇)

atomic qemu
1个回答
0
投票

它与

__atomic_thread_fence(__ATOMIC_SEQ_CST);
是多余的,它不允许任何操作在任一方向上重新排序。 但没有什么坏处,所以最好还是保留它以保持一致性。

它与 RELEASE

ACQUIRE
栅栏
不是
多余。 从理论上讲,即使是
ACQ_REL
栅栏也允许对较早的商店和较晚的负载进行重新排序 (StoreLoad)。 因此,编译器可以在编译时执行此操作,并且不会发出指令来阻止它在运行时发生。

但是 Linux 内核对

smp_rmb()
smp_wmb()
的定义是根据
asm("..." ::: "memory")
GNU C 内联汇编来阻止所有编译时重新排序的。
Linux
barrier()
定义为
asm("" ::: "memory")


实际上,GCC 可能将任何

__atomic_thread_fence
视为完整的编译器障碍;请参阅 gcc 是否将宽松的原子操作视为编译器栅栏? - GCC 目前甚至不会在
relaxed
操作之前和之后优化同一变量的增量。 但是 Clang 会优化。

差异的实际演示

int read_twice(int* x) {
  int tmp = *x;
    //barrier();
    __atomic_thread_fence(__ATOMIC_RELEASE); // Doesn't block LoadLoad
  tmp += *x;
  return tmp;
}

最新的 GCC 加载两次。
Clang 正确地将其优化为没有

barrier()
的单个负载,但不能使用它。 (神箭)

# x86-64 clang 19, NO barrier()
read_twice(int*):
        mov     eax, dword ptr [rdi]
        add     eax, eax
        ret
# x86-64 clang 19, WITH barrier()
read_twice_barrier(int*):
        mov     eax, dword ptr [rdi]
        add     eax, dword ptr [rdi]
        ret

显然这是一个愚蠢的例子,其中屏障没有任何意义,但请记住,在内联小函数后可以进行优化。

没有

barrier()
就会中断的代码可能已经不安全了, 例如可能使用非原子(和非
volatile
)访问共享变量而不同步。 在正确使用栅栏(和/或具有适当内存顺序的原子加载)的代码中,在没有
barrier()
的情况下允许的优化仍然是安全的。

另请参阅谁害怕一个糟糕的优化编译器?回复:对共享数据进行普通访问的危险:除了明显的陷阱之外,还可能存在微妙的影响,例如发明的加载,其中临时被优化掉并且编译器重新加载共享数据。

但无论如何,为了与 Linux 内核完全严格兼容

smp_*
内存屏障功能,阻止它们之间的所有编译时重新排序是正确的,而不是多余的。


相关:

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.