内核的有些函数隐含内存屏障。
(1)获取和释放函数。
(2)中断禁止函数。
1.获取和释放函数
获取( acquire)函数包括如下。
(1)获取锁的函数。锁包括自旋锁、读写自旋锁、互斥锁、信号量和读写信号量。
(2) smp_load_acquire(p):加载获取。
(3) smp_cond_load_acquire(ptr, cond_expr):带条件的加载获取。
获取操作隐含如下。
(1)获取操作后面的内存访问操作只能在获取操作完成之后被观察到。
(2)获取操作前面的内存访问操作可能在获取操作完成之后被观察到。
释放(release)函数包括如下。
(1)释放锁的函数。
(2) smp_store_release(p, v):存储释放。
释放操作隐含如下。
(1)释放操作前面的内存访问操作必须在释放操作完成之前被观察到。
(2)释放操作后面的内存访问操作可能在释放操作完成之前被观察到。
获取操作和释放操作都是单向屏障。
有返回值的atomic操作
主要指以下方法:
? ? ? ? xchg();
? ? ? ? cmpxchg();
? ? ? ? atomic_cmpxchg();
? ? ? ? atomic_inc_return();
? ? ? ? atomic_dec_return();
? ? ? ? atomic_add_return();
? ? ? ? atomic_sub_return();
? ? ? ? atomic_inc_and_test();
? ? ? ? atomic_dec_and_test();
? ? ? ? atomic_sub_and_test();
? ? ? ? atomic_add_negative();
? ? ? ? atomic_add_unless(); ? ?/* when succeeds (returns 1) */
? ? ? ? test_and_set_bit();
? ? ? ? test_and_clear_bit();
? ? ? ? test_and_change_bit();
而这些方法是不隐含内存屏障的:
? ? ? ? atomic_set();
? ? ? ? set_bit();
? ? ? ? clear_bit();
? ? ? ? change_bit();
? ? ? ? atomic_add();
? ? ? ? atomic_sub();
? ? ? ? atomic_inc();
? ? ? ? atomic_dec();
2.中断禁止函数
禁止中断和开启中断的函数只充当编译器优化屏障。
我们熟悉了MESI状态的转换的之后,我们来看cache伪共享就简单多了。什么是cache伪共享呢?其实,我们知道一个cache line的大小是32字节或者64字节,如果两个频繁访问的数据A和B,他们共处在一个cache line里面,然后不同的CPU都在频繁的访问A或者B的数据,那么就会带来性能上的问题,可能这个cache line的状态要频繁的变来变去,造成一种无畏的颠簸,我们知道MESI本质上是要消耗系统内部总线的带宽的,你一个cache line的状态老是频繁的变来变去,总线带宽都被你消耗了不少,当然会引起性能的问题,所以,这个叫做cache伪共享(英文叫做false sharing)。
伪共享的避免:
第一个案例是cache的伪共享的避免。避免的方法主要有两个。
第一个是:一些常用的数据结构在定义时就约定数据结构以一级缓存对齐。例如使用如下的宏来让数据结构首地址以L1 cache对齐。下面这个宏是利用了GCC的特性,_attribute的属性,来让数据结构的起始地址以某个数字对齐,这里是以L1 cache对齐。
??
第二个是:数据结构中频繁访问的成员可以单独占用一个高速缓存行,或者相关的成员在高速缓存行中彼此错开,以提高访问效率。
例如struct zone数据结构使用ZONE_PADDING技术(填充字节的方式)来让频繁访问的成员在不同的cache line中。
??
所以,cache伪共享,在有些情况下是性能杀手,而且你又比较难去发现它,所以需要我们编程的时候,特别注意。你写的数据结构里,有没有可能出现 不同的CPU核心频繁访问某些成员,导致cache伪共享的?这个需要写程序的时候就要思考清楚。
?