顺序和屏障

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

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

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

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

 

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

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

 

  1. 在<Alternative.h(includeasm-i386)>中
  2. /*
  3.  * Alternative instructions for different CPU types or capabilities.
  4.  *
  5.  * This allows to use optimized instructions even on generic binary
  6.  * kernels.
  7.  *
  8.  * length of oldinstr must be longer or equal the length of newinstr
  9.  * It can be padded with nops as needed.
  10.  *
  11.  * For non barrier like inlines please define new variants
  12.  * without volatile and memory clobber.
  13.  */
  14. #define alternative(oldinstr, newinstr, feature)    
  15.     asm volatile ("661:nt" oldinstr "n662:n"             
  16.               ".section .altinstructions,"a"n"            
  17.               "  .align 8n"                       
  18.               "  .quad 661bn"            /* label */          
  19.               "  .quad 663fn"        /* new instruction */ 
  20.               "  .byte %c0n"             /* feature bit */    
  21.               "  .byte 662b-661bn"       /* sourcelen */      
  22.               "  .byte 664f-663fn"       /* replacementlen */ 
  23.               ".previousn"                 
  24.               ".section .altinstr_replacement,"ax"n"     
  25.               "663:nt" newinstr "n664:n"   /* replacement */ 
  26.               ".previous" :: "i" (feature) : "memory")

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

 

  1. /*
  2.  * Force strict CPU ordering.
  3.  * And yes, this is required on UP too when we’re talking
  4.  * to devices.
  5.  *
  6.  * For now, "wmb()" doesn’t actually do anything, as all
  7.  * Intel CPU’s follow what Intel calls a *Processor Order*,
  8.  * in which all writes are seen in the program order even
  9.  * outside the CPU.
  10.  *
  11.  * I expect future Intel CPU’s to have a weaker ordering,
  12.  * but I’d also expect them to finally get their act together
  13.  * and add some real memory barriers if so.
  14.  *
  15.  * Some non intel clones support out of order store. wmb() ceases to be a
  16.  * nop for these.
  17.  */

 

  1. #ifdef CONFIG_X86_OOSTORE
  2. /* Actually there are no OOO store capable CPUs for now that do SSE, 
  3.    but make it already an possibility. */
  4. #define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
  5. #else
  6. #define wmb()   __asm__ __volatile__ ("": : :"memory")
  7. #endif

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

 

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

 

  1. /* 
  2.  * Actually only lfence would be needed for mb() because all stores done 
  3.  * by the kernel should be already ordered. But keep a full barrier for now. 
  4.  */
  5.  
  6. #define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)

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

 

  1. /**
  2.  * read_barrier_depends – Flush all pending reads that subsequents reads
  3.  * depend on.
  4.  *
  5.  * No data-dependent reads from memory-like regions are ever reordered
  6.  * over this barrier.  All reads preceding this primitive are guaranteed
  7.  * to access memory (but not necessarily other CPUs’ caches) before any
  8.  * reads following this primitive that depend on the data return by
  9.  * any of the preceding reads.  This primitive is much lighter weight than
  10.  * rmb() on most CPUs, and is never heavier weight than is
  11.  * rmb().
  12.  *
  13.  * These ordering constraints are respected by both the local CPU
  14.  * and the compiler.
  15.  *
  16.  * Ordering is not guaranteed by anything other than these primitives,
  17.  * not even by data dependencies.  See the documentation for
  18.  * memory_barrier() for examples and URLs to more information.
  19.  *
  20.  * For example, the following code would force ordering (the initial
  21.  * value of "a" is zero, "b" is one, and "p" is "&a"):
  22.  *
  23.  * <programlisting>
  24.  *  CPU 0               CPU 1
  25.  *
  26.  *  b = 2;
  27.  *  memory_barrier();
  28.  *  p = &b;             q = p;
  29.  *                  read_barrier_depends();
  30.  *                  d = *q;
  31.  * </programlisting>
  32.  *
  33.  * because the read of "*q" depends on the read of "p" and these
  34.  * two reads are separated by a read_barrier_depends().  However,
  35.  * the following code, with the same initial values for "a" and "b":
  36.  *
  37.  * <programlisting>
  38.  *  CPU 0               CPU 1
  39.  *
  40.  *  a = 2;
  41.  *  memory_barrier();
  42.  *  b = 3;              y = b;
  43.  *                  read_barrier_depends();
  44.  *                  x = a;
  45.  * </programlisting>
  46.  *
  47.  * does not enforce ordering, since there is no data dependency between
  48.  * the read of "a" and the read of "b".  Therefore, on some CPUs, such
  49.  * as Alpha, "y" could be set to 3 and "x" to 0.  Use rmb()
  50.  * in cases like this where there are no data dependencies.
  51.  **/
  52.  
  53. #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(includelinux)>中
  2. /* Optimization barrier */
  3. #ifndef barrier
  4. # define barrier() __memory_barrier()
  5. #endif
  6.  

 

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

发表评论

电子邮件地址不会被公开。 必填项已用*标注