1.ARM指令的实现
由于一些编译器优化或者CPU设计的流水线乱序执行,导致最终内存的访问顺序可能和代码中的逻辑顺序不符,所以需要增加内存屏障指令来保证顺序性。
ARM平台上存在三种内存屏障指令:
DMB{cond} {option}
这种指令只影响到了内存访问指令的顺序,保证在此指令前的内存访问完成后才执行后面的内存访问指令。
DSB{cond} {option}
比DMB更加严格,保证在此指令前的内存访问指令/cache/TLB/分支预测指令都完成,然后才会执行后面的所有指令。
ISB{cond} {option}
最为严格的一种,冲洗流水线和预取buffer,然后才会从cache或者内存中预取ISB后面的指令。
option的选择:
2.linux内核的实现
2.1 dmb/dsb/isb
Linux将不同类型的内存屏障和ARMv8提供的屏障指令都做了映射(代码位于arch/arm64/include/asm/barrier.h中):
#define isb() asm volatile("isb" : : : "memory")
#define dmb(opt) asm volatile("dmb " #opt : : : "memory")
#define dsb(opt) asm volatile("dsb " #opt : : : "memory")
......
#define spec_bar() asm volatile(ALTERNATIVE("dsb nsh\nisb\n", \
SB_BARRIER_INSN"nop\n", \
ARM64_HAS_SB))
#define mb() dsb(sy)
#define rmb() dsb(ld)
#define wmb() dsb(st)
#define dma_mb() dmb(osh)
#define dma_rmb() dmb(oshld)
#define dma_wmb() dmb(oshst)
......
#define __smp_mb() dmb(ish)
#define __smp_rmb() dmb(ishld)
#define __smp_wmb() dmb(ishst)
1.dsb非常严格的完成屏障,mb()保证了前面的指令的完成,前面的指令不必是load,store,比如可以是TLBI。dsb(ld)、dsb(st)则要弱一点,分别保证前面的load,store执行完了才执行后面的指令。
2.dma_***此屏障主要用于运行Linux的多个核与DMA引擎之间的保序,所以它主要是dmb,它是osh,通过ld、st来区分保序的方向。
3.__smp_***此屏障主要用于运行Linux的多个核之间对内存访问的保序,所以它主要是dmb,它是ish,通过ld、st来区分保序的方向。
2.2 单向屏障(load_acquire/store_release)
前面的所有屏障指令对于ARMv8之前所有的架构都有效,在ARMv8架构下,还加入了一类单向屏障指令,也就是所谓的Load-Acquire(LDAR指令)和Store-Release(STLR指令)。
普通的内存屏障一般是双向的,也就是可以控制内存屏障之前的某些存储操作要在内存屏障之前完成,并且内存屏障之后的某些存储操作要在内存屏障之后才能开始。但是Load-Acquire和Store-Release却只限定了单个方向的:
Load-Acquire:这条指令之后的所有加载和存储操作一定不会被重排序到这条指令之前,但是没有要求这条指令之前的所有加载和存储操作一定不能被重排序到这条指令之后。所以,可以看出来,这条指令是个单向屏障,只挡住了后面出现的所有内存操作指令,但是没有挡住这条指令之前的所有内存操作指令。
Store-Release:这条指令之前的所有加载和存储才做一定不会被重排序到这条指令之后,但是没有要求这条指令之后的所有加载和存储操作一定不能被重排序到这条指令之前。所以,这条指令也是一个单向屏障,只挡住了前面出现的所有内存操作指令,但是没有挡住这条指令之后的所有内存操作指令。
LDAR和STLR指令也有作用的共享域,只不过没有明确在指令中表示出来。这两条指令的共享域就是它们操作的内存的共享域。比如,如果LDAR指令读取内存的地址是属于内部共享域的,那么这条指令锁提供的屏障也只是作用于这个内部共享域。
还有一点,如果LDAR指令出现在STLR指令之后,处理器也会保证LDAR指令一定不会被重排序到STLR指令之前。
单向屏障的作用范围可以总结为下面这张图:
2.2.1 smp_load_acquire
#define smp_load_acquire(p) __smp_load_acquire(p)
#define READ_ONCE(var) (*((volatile typeof(var) *)(&(var))))
#define __smp_load_acquire(p) \
({ \
union { __unqual_scalar_typeof(*p) __val; char __c[1]; } __u; \
typeof(p) __p = (p); \
compiletime_assert_atomic_type(*p); \
kasan_check_read(__p, sizeof(*p)); \
switch (sizeof(*p)) { \
case 1: \
asm volatile ("ldarb %w0, %1" \
: "=r" (*(__u8 *)__u.__c) \
: "Q" (*__p) : "memory"); \
break; \
case 2: \
asm volatile ("ldarh %w0, %1" \
: "=r" (*(__u16 *)__u.__c) \
: "Q" (*__p) : "memory"); \
break; \
case 4: \
asm volatile ("ldar %w0, %1" \
: "=r" (*(__u32 *)__u.__c) \
: "Q" (*__p) : "memory"); \
break; \
case 8: \
asm volatile ("ldar %0, %1" \
: "=r" (*(__u64 *)__u.__c) \
: "Q" (*__p) : "memory"); \
break; \
} \
(typeof(*p))__u.__val; \
})
2.2.2 smp_store_release
#define smp_store_release(p, v) __smp_store_release(p, v)
do { \
typeof(p) __p = (p); \
union { __unqual_scalar_typeof(*p) __val; char __c[1]; } __u = \
{ .__val = (__force __unqual_scalar_typeof(*p)) (v) }; \
compiletime_assert_atomic_type(*p); \
kasan_check_write(__p, sizeof(*p)); \
switch (sizeof(*p)) { \
case 1: \
asm volatile ("stlrb %w1, %0" \
: "=Q" (*__p) \
: "r" (*(__u8 *)__u.__c) \
: "memory"); \
break; \
case 2: \
asm volatile ("stlrh %w1, %0" \
: "=Q" (*__p) \
: "r" (*(__u16 *)__u.__c) \
: "memory"); \
break; \
case 4: \
asm volatile ("stlr %w1, %0" \
: "=Q" (*__p) \
: "r" (*(__u32 *)__u.__c) \
: "memory"); \
break; \
case 8: \
asm volatile ("stlr %1, %0" \
: "=Q" (*__p) \
: "r" (*(__u64 *)__u.__c) \
: "memory"); \
break; \
} \
} while (0)
参考: