Memory management 为什么我们不在用户空间使用屏障

Memory management 为什么我们不在用户空间使用屏障,memory-management,cpu-architecture,cpu-cache,memory-barriers,barrier,Memory Management,Cpu Architecture,Cpu Cache,Memory Barriers,Barrier,我正在阅读有关内存障碍的文章,我可以总结的是,它们阻止了编译器对指令进行重新排序 所以在用户空间内存中,让我们假设我有 b = 0; main(){ a = 10; b = 20; c = add(a,b); } 编译器是否可以对该代码重新排序,以便在调用c=add()后执行b=20赋值 在这种情况下,我们为什么不使用屏障?我缺少一些基本的东西 虚拟内存是否免于任何重新排序 进一步扩展问题: 在网络驱动程序中: 1742 /* 1743 * Writing

我正在阅读有关内存障碍的文章,我可以总结的是,它们阻止了编译器对指令进行重新排序

所以在用户空间内存中,让我们假设我有

b = 0;
main(){

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

}
编译器是否可以对该代码重新排序,以便在调用
c=add()
后执行
b=20
赋值

在这种情况下,我们为什么不使用屏障?我缺少一些基本的东西

虚拟内存是否免于任何重新排序

进一步扩展问题:

在网络驱动程序中:

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 

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

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

这种对重新排序的限制只适用于执行代码的线程。正如@GabrielSouthern所指出的,如果
a
b
、和
c
都是全局变量,则无法保证门店的全球可见顺序。

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

其他细节 当分析操作的可能排序时有两件事要考虑:

  • 执行代码的线程将以什么顺序看到操作
  • 其他线程将在中看到操作的顺序是什么
  • 在您的示例中,编译器无法将
    b=20
    重新排序为在
    c=add(a,b)
    之后出现,因为
    c=add(a,b)
    操作使用
    b=20
    的结果。但是,编译器可能会对这些操作重新排序,以便其他线程在与
    b
    相关的内存位置更改之前看到与
    c
    相关的内存位置更改

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

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

    b = 0;
    main(){
    
    a = 10;
    b = 20;
    d = 30;
    c = add(a,b);
    
    }
    
    在这种情况下,编译器可以自由地将
    d=30
    赋值移动到
    c=add(a,b)
    之后

    但是,整个示例过于简单化。程序不做任何事情,编译器可以消除所有操作,并且不需要向内存写入任何内容

    附录:内存重新排序示例 在多处理器环境中,多个线程可以看到内存操作以不同的顺序发生。第3卷第8.2.3节中有一些示例。我复制了下面的一个屏幕截图,显示了一个加载和存储可以重新排序的示例。 还有一个例子提供了关于这个例子的更多细节


    运行代码的线程将始终像其自身代码的源代码行的效果按程序顺序发生一样。这就好像规则是大多数编译器优化的基础

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

    只有在通过共享内存与其他线程交互的代码中才需要内存屏障(作为锁定的一部分,或者单独使用)

    。C++内存模型非常弱,所以即使在瞄准x86 CPU时,编译时重新排序也是可能的。(当然不是在本地线程中产生不同结果的重新排序。)C11
    和等效的C++11
    std::atomic
    是告诉编译器关于操作全局可见性的任何排序要求的最佳方法。在x86上,这通常只会导致将存储指令按源代码顺序放置,但默认的
    内存顺序
    需要在每个存储上设置
    MFENCE
    ,以防止存储加载重新排序以实现完整的顺序一致性


    在内核代码中,内存障碍也很常见,以确保内存映射I/O寄存器的存储按要求的顺序进行。推理是一样的:对存储和加载序列对内存的全局可见影响进行排序。区别在于观察者是一个I/O设备,而不是另一个CPU上的线程。核心通过缓存一致性协议相互交互这一事实并不重要。

    内存障碍不仅仅是编译器重新排序的问题。在多线程程序中,不同的线程可以看到不同的内存访问顺序(对于大多数ISA)。对于编译器重新排序,重新排序在一个线程中不可见,但在其他线程中可能可见。例如,如果函数使用常量,编译器可以预先计算
    add(a,b)
    并首先存储结果,然后存储
    a
    b
    ,这样即使在顺序一致的处理器上,另一个线程也会在
    a
    b
    更改之前看到
    c
    的更改,我看到屏障主要用于设备或RAM的内存访问,那么多线程编程适合于哪里呢?这是一个广泛的问题,但基本上-在单线程上下文中有一个隐式顺序,一个编译器可以而且必须保留的顺序,但在不同线程上的操作之间没有隐式顺序,因此,编译器和硬件都不能强制执行,除非您告诉他们如何执行。他们所能做的最好的事情就是决定一些随机顺序,并使其看起来一致。re:你的编辑。读我答案的最后一段。那正是