练家志 发表于 2017-9-26 16:49:53

优化屏障和内存壁垒

本帖最后由 练家志 于 2017-9-26 16:51 编辑

当使用指令优化的编译器时,你千万不要认为指令会严格按它们在源代码中出现的顺序执行。例如,编译器可能重新安排汇编语言指令以使寄存器以最优的方式使用。此外,现代CPU通常并行地执行若干条指令,且可能重新安排内存访问。这种重新排序可以极大地加速程序的执行。

然而,当处理同步时,必须避免指令重新排序。因为如果放在同步及原语之后的一条指令在同步原语本身之前执行,事情很快就会变得失控。所以,所有的同步技术都应该避免指令优化后的乱序,这里用到一个优化屏障和内存壁垒技术。
1 优化屏障

优化屏障(optimization barrier)技术保证编译器不会混淆存放在同步操作之前的汇编语言指令和存放在同步操作之后的汇编语言指令,这些汇编语言指令在C语言中都有对应的语句。

在Linux中,优化屏障就是barrier()宏,C编译器它展开为asm volatile("":::"memory"):
#define barrier() __asm__ __volatile__("": : :"memory")

指令的具体内容其实是一个空的指令,asm告诉编译程序要插入汇编语言片段(这种情况下为空)。volatile关键字禁止编译器把asm指令与程序中的其他指令重新组合。memory关键字强制编译器假定RAM中的所有内存单元已经被汇编语言指令修改。

因此,执行优化屏障宏后,编译器不能使用存放在CPU寄存器中的内存单元的值来优化asm指令前的代码。注意,优化屏障并不保证不使当前CPU把汇编语言指令混在一起执行——避免CPU乱序执行是内存壁垒的工作。
2 内存壁垒

内存壁垒(memory barrier)技术确保,在进入临界区之后的操作开始执行之前,临界区之前的操作已经完成。因此,内存壁垒类似于防火墙,让任何汇编语言指令都不能通过。在80x86处理器中,下列种类的汇编语言指令是“串行的”,因为它们起内存壁垒的作用:
(1)对I/O端口进行操作的所有指令。
(2)有lock前缀的所有指令。
(3)写控制寄存器、系统寄存器或调试寄存器的所有指令(例如,cli和sti,用于修改eflags寄存器的正标志的状态)。
(4)在Pentium 4微处理器中引入的汇编语言指令lfence、sfence和mfence,它们分别有效地实现读内存壁垒、写内存壁垒和读-写内存壁垒。
(5)少数专门的汇编语言指令,终止中断处理程序或异常处理程序的iret指令就是其中的一个。

Linux使用六个内存壁垒宏,如下所示。当然,这些宏也被当作优化屏障,因为我们必须保证编译器不在壁垒前后移动汇编语言指令。“读内存壁垒”仅仅作用于从内存读的指令,而“写内存壁垒”仅仅作用于写内存的指令。内存壁垒既用于多处理器系统,也用于单处理器系统。当内存壁垒应该防止仅出现于多处理器系统上的竞争条件时,就使用smp_xxx()宏;在单处理器系统上,它们什么也不做。其他的内存壁垒防止出现在单处理器和多处理器系统上的竞争条件。

mb( ):适用于 MP 和 UP的内存壁垒
rmb( ):适用于 MP和 UP的内存壁垒
wmb( ):适用于 MP和 UP的内存壁垒
smp_mb( ):仅适用于MP的内存壁垒
smp_rmb( ):仅适用于MP的内存壁垒
smp_wmb( ):仅适用于MP的内存壁垒

#define alternative(oldinstr, newinstr, feature)            \
    asm volatile ("661:\n\t" oldinstr "\n662:\n"             \
            ".section .altinstructions,\"a\"\n"      \
            ".align 4\n"                  \
            ".long 661b\n"            /* label */      \
            ".long 663f\n"          /* new instruction */    \
            ".byte %c0\n"             /* feature bit */    \
            ".byte 662b-661b\n"       /* sourcelen */    \
            ".byte 664f-663f\n"       /* replacementlen */    \
            ".previous\n"                  \
            ".section .altinstr_replacement,\"ax\"\n"      \
            "663:\n\t" newinstr "\n664:\n"   /* replacement */\
            ".previous" :: "i" (feature) : "memory")


#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)
#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)

#ifdef CONFIG_SMP
#define smp_mb()    mb()
#define smp_rmb()    rmb()
#define smp_wmb()    wmb()
#define smp_read_barrier_depends()    read_barrier_depends()
#define set_mb(var, value) do { (void) xchg(&var, value); } while (0)
#else
#define smp_mb()    barrier()
#define smp_rmb()    barrier()
#define smp_wmb()    barrier()
#define smp_read_barrier_depends()    do { } while(0)
#define set_mb(var, value) do { var = value; barrier(); } while (0)
#endif

内存壁垒技术的实现依赖于系统的体系结构。在80x86微处理器上,如果CPU支持lfence汇编语言指令,就把rmb()宏展开为asm volatile("lfence") ,否则就展开为asm volatile("lock;addl $0,0(%%esp)":::"memory") 。asm指令告诉编译器插人一些汇编语言指令并起优化屏障的作用。lock;addl $0,0(%%esp) 汇编指令把0加到栈顶的内存单元;这条指令本身没有价值,但是,lock前缀使得这条指令成为CPU的一个内存屏障。

Intel上的wmb()宏实际上更简单,因为它展开为barrier()。这是因为Intel处理器从不对写内存访问重新排序,因此,没有必要在代码中插入一条串行化汇编指令。不过,这个宏禁止编译器重新组合指令。

注意,在多处理器系统上,中描述的所有原子操作都起内存屏障的作用,因为它们使用了lock字节。
页: [1]
查看完整版本: 优化屏障和内存壁垒