顺序和屏障 收藏

   当处理多处理器之间或硬件设备之间的同步问题时,有时需要在程序代码中以指定的顺序发出读内存(读入)和写内存(存储)指令。在和硬件交互时,时常需要确保一个给定的读操作发生在其他读或写操作之前。另外,在多处理上,可能需要按写数据时的顺序读数据(通常确保后来以同样的顺序进行读取)。但是编译器和处理器为了提高效率,可能对读和写重新排序。   

所有可能重新排序和写的处理器提供了及其指令来确保顺序要求。同样也可以指示编译器不要对给定的点周围的指令进行重新排序,这些确保顺序的指令称为屏障(barrier)。

编译器会在编译时按代码的顺序编译,这种顺序是静态的。但是处理器会重新动态排序,因为处理器在执行指令期间,会在取值和分派时,把表面上看似无关的指令按自认为最好的顺序排列。这种重排序的发生是因为现代处理器为了优化其传送管道,打乱了分派和提交指令的顺序。

  不管是编译器还是处理器都不知道其他上下文中的相关代码。偶然情况下,有必要让写操作被其他代码识别,也让我们所期望的指定顺序之外的代码识别。这种情况常常发生在硬件设备上,但那是在多处理器机器上也很常见。

 

rmb()方法提供了一个“读”内存屏障,它确保跨越rmb()的载入动作不会发生重排序。在rmb()之前的载入操作不会被重新排在该调用之后;在rmb()之后的载入操作不会被重新排列在该调用之前。

  1. 在<System.h(include\asm-i386)>中
  2. #define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2) 

 

  1. 在<Alternative.h(include\asm-i386)>中
  2. #define alternative(oldinstr, newinstr, feature)    \
  3.     asm volatile ("661:\n\t" oldinstr "\n662:\n"             \
  4.               ".section .altinstructions,\"a\"\n"            \
  5.                .align 8\n"                       \
  6.                .quad 661b\n"                      \
  7.                .quad 663f\n"         \
  8.                .byte �\n"                 \
  9.                .byte 662b-661b\n"             \
  10.                .byte 664f-663f\n"        \
  11.               ".previous\n"                 \
  12.               ".section .altinstr_replacement,\"ax\"\n"     \
  13.               "663:\n\t" newinstr "\n664:\n"    \
  14.               ".previous" :: "i" (feature) "memory")

wmb()方法提供了一个“写”内存屏障。该函数与rmb()类型,区别仅仅是它是针对存储而非载入。它确保跨越屏障的存储不发生重新排序。如果一个体系结构不执行打乱存储(比如Intel x86芯片就不会),那么wmb()就什么也补做。

 

     

    1. #ifdef CONFIG_X86_OOSTORE
    2. #define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
    3. #else
    4. #define wmb()   __asm__ __volatile__ ("": :"memory")
    5. #endif

    rmb()和wmb()方法相当于指令,它们告诉处理器在继续执行前提交所有尚未处理的载入或存储指令。

     

    mb()方法既提供了读屏障也提供了写屏障。载入和存储动作都不会跨越屏障重新排序。这是因为一条单独的指令(通常和rmb()使用同一个指令)既可以提供载入屏障,也可以提供存储屏障。

     

    1. #define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)

    read_barrier_depends()是rmb()的变种,它提供一个读屏障,但是仅仅是针对后续读操作锁依赖的那些载入。因为屏障后的读操作依赖于屏障前的读操作,因此,该屏障确保屏障前的读操作在屏障后的读操作之前完成。基本上说,该函数设置一个读屏障,如rmb(),但是只真对特定的读---也就是那些相互依赖的读操作。

     

    1. #define read_barrier_depends()  do while(0)

    第2个<programlisting>主要是为了说明read_barrier_depends()用于数据依赖的读操作,由于y和x不是数据依赖的,因为没有成功设置读屏障,导致x=a在y=b之前运行,于是a可能还是0的时候就被赋给x了。此时,对于没有数据依赖的读操作应该使用rmb()来提供读屏障。

    宏smp_rmb()、smp_wbm()、smp_mb()和smp_read_barrier_depends()提供了一个有用的优化。在SMP 内核中,它们被定义成常用的内存屏障,而在单处理器内核中,它们被定义成编译器的屏障

     

    1. #ifdef CONFIG_SMP    //在SMP 内核中,它们被定义成常用的内存屏障
    2. #define smp_mb()    mb()
    3. #define smp_rmb()   rmb()
    4. #define smp_wmb()   wmb()
    5. #define smp_read_barrier_depends()  read_barrier_depends()
    6. #define set_mb(var, value) do (void) xchg(&var, value); while (0)
    7. #else    //在单处理器内核中,它们被定义成编译器的屏障
    8. #define smp_mb()    barrier()
    9. #define smp_rmb()   barrier()
    10. #define smp_wmb()   barrier()
    11. #define smp_read_barrier_depends()  do while(0)
    12. #define set_mb(var, value) do var value; barrier(); while (0)
    13. #endif

    barrier()方法可以防止编译器跨越屏障对载入或存储操作进行优化。编译器不会重新组织存储或载入操作而防止改变C代码的效果和现有数据的依赖关系。但是,它不知道当前上下文之外会发生什么事。前面讨论的内存屏障可以完成编译器屏障的功能,但是后者比前者轻量得多。实际上,编译器屏障机会是空闲的,因为它只是防止编译器可能重排指令。

    1. 在<Compiler.h(include\linux)>中
    2. #ifndef barrier
    3. define barrier() __memory_barrier()
    4. #endif

     

    为最坏的情况(即排序能力最弱的处理器)使用恰当的内存屏蔽,这样代码才能在编译时执行针对体系结构的优化。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值