READ_ONCE,WRITE_ONCE和 ACCESS_ONCE 宏在linux内核中出现的频率极高。那么这三个宏到底起了什么样的作用呢?
smp_store_release/smp_load_acquire又如何呢?
1、宏定义
我们先看下READ_ONCE和WRITE_ONCE的宏定义:
#define READ_ONCE(x) \
({ union { typeof(x) __val; char __c[1]; } __u; __read_once_size(&(x), __u.__, sizeof(x)); __u.__val; })
#define WRITE_ONCE(x, val) \
({ typeof(x) __val = (val); __write_once_size(&(x), &__val, sizeof(__val)); __val; })
内部涉及到__read_once_size和__write_once_size两个函数,那我们再看下这两个函数实现:
/*p为待读取数据地址,res保存读取结果,size为读取数据长度*/
static __always_inline void __read_once_size(const volatile void *p, void *res, int size)
{
switch (size) {
case 1: *(__u8 *)res = *(volatile __u8 *)p; break;
case 2: *(__u16 *)res = *(volatile __u16 *)p; break;
case 4: *(__u32 *)res = *(volatile __u32 *)p; break;
#ifdef CONFIG_64BIT
case 8: *(__u64 *)res = *(volatile __u64 *)p; break;
#endif
default:/*超过系统支持长度*/
barrier();
__builtin_memcpy((void *)res, (const void *)p, size);
data_access_exceeds_word_size(); /*产生警告*/
barrier();
}
}
/*p为待写入数据地址,res待写入数据值,size为写入数据长度*/
static __always_inline void __write_once_size(volatile void *p, void *res, int size)
{
switch (size) {
case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
#ifdef CONFIG_64BIT
case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
#endif
default: /*超过系统支持长度*/
barrier();
__builtin_memcpy((void *)p, (const void *)res, size);
data_access_exceeds_word_size(); /*产生警告*/
barrier();
}
}
static __always_inline void data_access_exceeds_word_size(void)
#ifdef __compiletime_warning
__compiletime_warning("data access exceeds word size and won't be atomic")
#endif
;
__read_once_size和__write_once_size两个函数中对地址p都使用volatile进行了修饰,这是一个编译的优化,避免CPU从cache缓存中取值而是到其所在内存地址中去取值。
所以,READ_ONCE和WRITE_ONCE的含义如下:
/*获取变量x的值,内部实现通过volatile修饰x的地址,避免编译器的优化,从x所在地址重新获取其值*/
#define READ_ONCE(x) \
({ union { typeof(x) __val; char __c[1]; } __u; __read_once_size(&(x)/*变量x的地址*/, __u.__c/*保存读取结果*/, sizeof(x)); __u.__val; })
/*将val赋值给x,内部将val值赋值给通过volatile修饰x的地址,避免编译器的优化,将val赋值给x的地址*/
#define WRITE_ONCE(x, val) \
({ typeof(x) __val = (val); __write_once_size(&(x), &__val, sizeof(__val)); __val; })
我们在来看看ACCESS_ONCE的宏定义:
/*返回x的值,内部通过获取volatile修饰的x的地址获取x的值,避免编译器的优化*/
#define ACCESS_ONCE(x) (*__ACCESS_ONCE(x))
#define __ACCESS_ONCE(x) ({ \
__maybe_unused typeof(x) __var = (__force typeof(x)) 0; \
(volatile typeof(x) *)&(x); })
2、smp_store_release和smp_load_acquire的作用
smp_store_release和smp_load_acquire也是内核中涉及内存屏障出现概率较高的函数,其内部是如何实现的呢?
我们先看下它们定义:
/*将v赋值给p所指向的内存空间*/
#define smp_store_release(p, v) \
do { \
compiletime_assert_atomic_type(*p); \
smp_mb(); \
ACCESS_ONCE(*p) = (v); \
} while (0)
/*返回p所指向内存空间的值*/
#define smp_load_acquire(p) \
({ \
typeof(*p) ___p1 = ACCESS_ONCE(*p); \
compiletime_assert_atomic_type(*p); \
smp_mb(); \
___p1; \
})
其中ACCESS_ONCE 上面已经讲述过,就是避免编译器优化,获取到正确的值。
大家仔细看,在两个函数中smp_mb的位置相对于ACCESS_ONCE是不同的,为什么?
"smp_store_release" 中的smp_mb用来防止barrier之前的store/release跑到barrier之后的store后面。
"smp_load_acquire" 中的smp_mb用来防止barrier之后的store/release跑到barrier之前的load前面。
它们可以算是 "smp_mb" 针对只需“单向”order保证的裁剪版本,被称作 one-way barrier。