为什么我们不在用户空间中使用障碍

时间:2016-03-22 09:37:34

标签: memory-management cpu-architecture cpu-cache memory-barriers barrier

我正在阅读有关内存障碍的内容,我可以总结的是它们会阻止编译器重新排序指令。

所以在用户空间内存让我说我有

b = 0;
main(){

a = 10;
b = 20;
c = add(a,b);

}

编译器是否可以重新排序此代码,以便在b = 20被调用后发生c = add()赋值。

为什么我们在这种情况下不使用障碍?我在这里错过了一些基本的东西。

虚拟内存是否免于重新订购?

进一步扩展问题:

在网络驱动程序中:

1742         /*
1743          * Writing to TxStatus triggers a DMA transfer of the data
1744          * copied to tp->tx_buf[entry] above. Use a memory barrier
1745          * to make sure that the device sees the updated data.
1746          */
1747         wmb();
1748         RTL_W32_F (TxStatus0 + (entry * sizeof (u32)),
1749                    tp->tx_flag | max(len, (unsigned int)ETH_ZLEN));
1750 

当他说设备看到更新的数据时...如何将这与使用障碍的多线程理论联系起来。

3 个答案:

答案 0 :(得分:3)

简短回答

用户模式代码中内存屏障的使用频率低于内核模式代码,因为用户模式代码倾向于使用更高级别的抽象(例如pthread同步操作)。

其他细节

在分析可能的操作顺序时,需要考虑两件事:

  1. 执行代码的线程将以什么顺序查看
  2. 中的操作
  3. 其他线程的顺序将在
  4. 中看到操作

    在您的示例中,编译器无法在b=20之后重新排序c=add(a,b),因为c=add(a,b)操作使用b=20的结果。但是,编译器可能会重新排序这些操作,以便其他线程在与c关联的内存位置发生更改之前,查看与b关联的内存位置。

    这是否真的发生取决于硬件实现的内存一致性模型。

    至于编译器何时可以进行重新排序,您可以想象添加另一个变量,如下所示:

    b = 0;
    main(){
    
    a = 10;
    b = 20;
    d = 30;
    c = add(a,b);
    
    }
    

    在这种情况下,编译器可以自由地将d=30赋值移到c=add(a,b)之后。

    然而,这整个例子太简单了。程序没有做任何事情,编译器可以消除所有操作,不需要向内存写任何东西。

    附录:内存重新排序示例

    在多处理器环境中,多个线程可以看到内存操作以不同的顺序发生。 Intel Software Developer's Manual在第3卷第8.2.3节中有一些例子。我复制了下面的截图,其中显示了可以重新排序加载和存储的示例。 还有一个good blog post提供了有关此示例的更多详细信息。

    Loads reordered with earlier store to different locations

答案 1 :(得分:2)

运行代码的线程将始终 这是,好像规则是大多数编译器优化的原因。

在单个线程中,无序CPU跟踪依赖关系,以便为线程提供所有指令按程序顺序执行的错觉。但是,全局可见(对其他核心上的线程)效果可能会被其他核心无序看到。

只有在通过共享内存与其他线程交互的代码中才需要内存障碍(作为锁定或自身的一部分)。

Compilers can similarly do any reordering / hoisting they want, as long as the results are the same。 C ++内存模型非常弱,因此即使面向x86 CPU,也可以进行编译时重新排序。 (但当然不能重新排序会在本地线程中产生不同的结果。)C11 <stdatomic.h>和等效的C ++ 11 std::atomic是告诉编译器有关您的任何排序要求的最佳方式。全球运营可见度。在x86上,这通常只会导致存储指令按源顺序排列,但默认memory_order_seq_cst在每个存储上需要MFENCE,以防止StoreLoad重新排序以实现完全顺序一致性。

在内核代码中,内存屏障也很常见,以确保内存映射I / O寄存器的存储按所需顺序发生。原因是相同的:在一系列存储和加载的内存中排序全局可见的效果。区别在于观察者是I / O设备,而不是另一个CPU上的线程。核心通过缓存一致性协议相互交互的事实是无关紧要的。

答案 2 :(得分:1)

编译器无法重新排序(运行时或cpu也不能),因此b=20位于c=add()之后,因为这会改变方法的语义,这是不允许的。 我会说,对于编译器(或运行时或cpu),你所描述的行为会使行为随机,这将是一件坏事。

这种重新排序限制仅适用于执行代码的线程。正如@GabrielSouthern指出的那样,如果abc都是全局变量,则无法保证商店的全局可见性。