/*
* 2020/12/4 15:07 qing
*
* 原子操作需要靠硬件实现
*/
/*
* atomic
*/
Linux内核中提供了各种各样的原子操作函数。除了最原始的读取和设置之外,还包括各种运算,以及位操作等等。而且有的原子操作还要返回操作过后变量的值,
有的要返回操作之前变量的值,如果再牵涉到内存屏障的问题,将这些因素组合起来,有非常多的原子操作函数。
/*
* Bus Lock(锁总线)
*/
CPU执行原子指令时,给总线上锁,这样在释放前,可以防止其它CPU的内存操作。
/*
* Cache Lock
*/
除了和IO紧密相关的(如MMIO),大部分的内存都是可以被cache的,由前面介绍的cache一致性原理,我们知道由cacheline处于Exclusive或Modified时,
该变量只有当前CPU缓存了数据,因此当进行原子操作时,发出Read Invalidate消息,使其它CPU上的缓存无效,cacheline变成Exclusive状态然后将该
cacheline上锁,接着就可以取数据,修改并写入cacheline,如果这时有其它CPU也进行原子操作,发出read invalidate消息,但由于当前CPU的
cacheline是locked状态,因此暂时不会回复消息,这样其它CPU就一直在等待,直到当前CPU完成,使cacheline变为unlocked状态。
/*
* 基本型
*/
基本型包括非RMW(Read Modifiy Write)的读写操作,以及RMW的算术和位操作等。
非RMW的操作很简单,只有两个,即用来读取的atomic_read()和用来写入的atomic_set()。
一般对单独变量的读取或写入操作本身都是原子的,如果代码中只对单个变量进行读写操作,而从来没对它使用RMW操作,那一般说明你用错了,
没必要使用内核提供的原子操作。
算术操作:包括加、减、递增和递减,命名形式是atomic_{add,sub,inc,dec}();
Bit位操作:包括与、或、异或和与非,命名形式是atomic_{and,or,xor,andnot}();
交换操作:包括交换(atomic_xchg())、比较交换(atomic_cmpxchg())和添加了返回成功与否的比较交换(atomic_try_cmpxchg())。
除了atomic_t类型外,Linux内核还支持对64位和长整形的atomic64_t和atomic_long_t类型,它们也都有对应的基本原子操作函数,
只不过函数前缀名分别是atomic64_和atomic_long_
static inline void atomic_add(int i, atomic_t *v)
{
atomic_add_return(i, v);
}
#ifndef atomic_add_return
#define atomic_add_return(...) \
__atomic_op_fence(atomic_add_return, __VA_ARGS__)
#endif
#ifndef __atomic_op_fence
#define __atomic_op_fence(op, args...) \
({ \
typeof(op##_relaxed(args)) __ret; \
smp_mb__before_atomic(); \
__ret = op##_relaxed(args); \
smp_mb__after_atomic(); \
__ret; \
})
#endif
/*
* 返回修改过后的值
*/
如果一个原子操作函数要返回修改过后的原子变量的值,那么该函数名会含有“_return”字符串,并且是在表示具体操作字符串的后面。
例如,atomic_add_return、atomic_dec_return_relaxed等。
/*
* 要返回修改之前的值
*/
如果一个原子操作函数要返回修改之前的原子变量的值,那么该函数名会含有“_fetch”字符串,并且是在表示具体操作字符串的前面。
例如,atomic_fetch_and、atomic_fetch_or_acquire等。
/*
* 有Acquire和Release单向屏障语义
*/
如果一个原子操作函数还需要包括Acquire或者Release单向屏障语义,那么该函数名会有“_acquire”或者“_release”后缀。
例如,atomic_xchg_acquire、atomic_cmpxchg_release等。
相反的,如果一个原子操作函数名有“_relaxed”后缀,表示这个函数没有被任何内存屏障保护,可以被任意重排序。
例如,atomic_add_return_relaxed、atomic_xchg_relaxed等。
只有当一个原子操作函数要返回值的时候才有可能添加_acquire、_release和_relaxed后缀。
/*
* 原子操作的重排序规则
*/
Linux内核的原子操作只保证对单一变量的某个操作是原子的,多个CPU同时操作时,不会出现中间的错误状态。但是,对这个原子操作本身,
并不一定保证其执行的顺序,在SMP系统下,有可能会出现重排序的问题。
因此,前面也提到过,有些原子操作函数自己就带了一定的内存屏障的语义。具体有没有带,带了多少,可以通过函数名看出来,具体规则如下:
非RMW的原子操作可以被任意重排序;
RMW的原子操作,如果没有返回值可以被任意重排序;
RMW的原子操作,如果有返回值,并且没有_relaxed、_acquire和_release后缀,是有内存屏障保护的,可以保证不被重排序;
RMW的原子操作,如果有返回值,且有_relaxed后缀,没有任何内存屏障保护,可以被任意重排序;
RMW的原子操作,如果有返回值,且有_acquire后缀,表示读(RMW中的R)操作是有Acquire单向屏障保护的;
RMW的原子操作,如果有返回值,且有_release后缀,表示写(RMW中的W)操作是有Release单向屏障保护的;
RMW的原子操作,如果有条件判断,那么条件是否的那部分会被任意重排序。
对于那些不提供内存屏障语义的原子操作来说,为了保证其本身不被重排序,还需要显式的在其前面或后面使用内存屏障。
Linux内核提供了两个函数,分别是smp_mb__before_atomic()用于原子操作函数的前面,和smp_mb__after_atomic()用于原子操作函数的后面。
/*
* ARMv8架构下原子操作的实现
*/
#define ATOMIC_INIT(i) { (i) }
/* atomic_read */
#define atomic_read(v) READ_ONCE((v)->counter)
#define __READ_ONCE(x, check) \
({ \
union { typeof(x) __val; char __c[1]; } __u; \
if (check) \
__read_once_size(&(x), __u.__c, sizeof(x)); \
else \
__read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})
#define READ_ONCE(x) __READ_ONCE(x, 1)
static __always_inline
void __read_once_size(const volatile void *p, void *res, int size)
{
__READ_ONCE_SIZE;
}
#define __READ_ONCE_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; \
case 8: *(__u64 *)res = *(volatile __u64 *)p; break; \
default: \
barrier(); \
__builtin_memcpy((void *)res, (const void *)p, size); \
barrier(); \
} \
})
/* atomic_set */
#define atomic_set(v, i) WRITE_ONCE(((v)->counter), (i))
#define WRITE_ONCE(x, val) \
({ \
union { typeof(x) __val; char __c[1]; } __u = \
{ .__val = (__force typeof(x)) (val) }; \
__write_once_size(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})
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;
case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
default:
barrier();
__builtin_memcpy((void *)p, (const void *)res, size);
barrier();
}
}
/*
* 废弃的函数,学习一下C语法 及 指令
*/
static inline int
__ll_sc_atomic_add_return_acquire(int i, atomic_t *v)
{
unsigned long tmp;
int result;
asm volatile(
" // 将v->counter预取到CPU缓存\n" \
" prfm pstl1strm, %2\n" \
" // 将v->counter独占的读入result中\n" \
"1: ldaxr %w0, %2\n" \
" // result = result + i\n" \
" add %w0, %w0, %w3\n" \
" // 将result的值独占的存入v->counter中\n" \
" // 如果存入时独占标记被清除则将tmp置1\n" \
" stxr %w1, %w0, %2\n" \
" // 如果tmp被置1则从头再次执行一遍\n" \
" cbnz %w1, 1b\n" \
) \
: "=&r" (result), "=&r" (tmp), "+Q" (v->counter) \
: __stringify(I) "r" (i) \
: "memory"); \
\
return result; \
}
static inline int \
__ll_sc_atomic_fetch_or_release(int i, atomic_t *v) \
{ \
unsigned long tmp; \
int val, result; \
\
asm volatile( \
" // 将v->counter预取到CPU缓存\n" \
" prfm pstl1strm, %3\n" \
" // 将v->counter独占的读入result中\n" \
"1: ldxr %w0, %3\n" \
" // val = result | i\n" \
" orr %w1, %w0, %w4\n" \
" // 将val的值独占的存入v->counter中\n" \
" // 如果存入时独占标记被清除则将tmp置1\n" \
" stlxr %w2, %w1, %3\n" \
" // 如果tmp被置1则从头再次执行一遍\n" \
" cbnz %w2, 1b\n" \
: "=&r" (result), "=&r" (val), "=&r" (tmp), "+Q" (v->counter) \
: __stringify(K) "r" (i) \
: "memory"); \
\
return result; \
}
/* arch_cmpxchg */
#define __cmpxchg_wrapper(sfx, ptr, o, n) \
({ \
__typeof__(*(ptr)) __ret; \
__ret = (__typeof__(*(ptr))) \
__cmpxchg##sfx((ptr), (unsigned long)(o), \
(unsigned long)(n), sizeof(*(ptr))); \
__ret; \
})
#define arch_cmpxchg(...) __cmpxchg_wrapper( _mb, __VA_ARGS__)
arch_cmpxchg最终被宏定义成了__cmpxchg_wrapper,而它又调用了__cmpxchg_mb函数:
#define __CMPXCHG_GEN(sfx) \
static __always_inline unsigned long __cmpxchg##sfx(volatile void *ptr, \
unsigned long old, \
unsigned long new, \
int size) \
{ \
switch (size) { \
case 1: \
return __cmpxchg_case##sfx##_8(ptr, old, new); \
case 2: \
return __cmpxchg_case##sfx##_16(ptr, old, new); \
case 4: \
return __cmpxchg_case##sfx##_32(ptr, old, new); \
case 8: \
return __cmpxchg_case##sfx##_64(ptr, old, new); \
default: \
BUILD_BUG(); \
} \
\
unreachable(); \
}
__CMPXCHG_GEN()
__CMPXCHG_GEN(_acq)
__CMPXCHG_GEN(_rel)
__CMPXCHG_GEN(_mb)
#undef __CMPXCHG_GEN