原子操作
如果共享资源(临界区)非常小,小到只有一个变量的话,就可以不用重量级的加锁- 解锁操作,而是直接使用原子操作。原子操作意味着整个操作的执行过程是原子的,不可中断的。
原子操作分为以下几种,下面一一介绍:
(1)通用原子操作
Linux 内核定义了 32 位的原子变量类型 atomic_t 和 64 位的原子变量类型 atomic64_t,另外还有 atomic_long_t,在 32 位内核中等价于 atomic_t 而在 64 位内 核中等价于 atomic64_t。
其代码如下:
typedef struct {
int counter;
} atomic_t;
typedef struct {
long counter;
} atomic64_t;
#if BITS_PER_LONG == 64
typedef atomic64_t atomic_long_t;
#else
typedef atomic_t atomic_long_t;
#endif
32位或者64位原子操作的API主要有以下几种:
/*初始化原子变量的值为 i*/
ATOMIC_INIT(i)/ATOMIC64_INIT(i)/ATOMIC_LONG_INIT(i)
/*返回原子变量*v 的值*/
atomic_read(v)/atomic64_read(v)/ atomic_long_read(v):
/*给原子变量赋值:将*v 的值设置成 i*/
atomic_set(v, i)/atomic64_set(v, i)/atomic_long_set(v, i):
/*给原子变量做加法:*v 的值增加 i*/
atomic_add(i, v)/atomic64_add(i, v)/atomic_long_add(i, v):
/*给原子变量做减法:*v 的值减少 i*/
atomic_sub(i, v)/atomic64_sub(i,v)/atomic_long_sub(i, v):
/*给原子变量做加法并测试:*v 的值增加 i,若结果为负返回 1,否则为 0*/
atomic_sub_and_test(i, v)/atomic64_sub_and_test(i,v)/atomic_long_sub_and_test(i, v):
/*给原子变量做加法并测试:*v 的值增加 i,若结果为负返回 1,否则为 0*/
atomic_add_negtive(i, v)/atomic64_add_negtive(i, v)/atomic_long_add_negtive(i, v):
/*给原子变量加一:*v 的值加一*/
atomic_inc(v)/atomic64_inc(v)/atomic_long_inc(v)
/*给原子变量减一:*v 的值减一*/
atomic_dec(v)/atomic64_dec(v)/atomic_long_dec(v)
/*给原子变量加一并测试:*v 的值加一,若结果为 0 返回 1,否则返回 0*/
atomic_inc_and_test(v)/atomic64_inc_and_test(v)/atomic_long_inc_and_tes t(v)
/*给原子变量减一并测试:*v 的值减一,若结果为 0 返回 1,否则返回 0*/
atomic_dec_and_test(v)/atomic64_dec_and_test(v)/atomic_long_dec_and_test(v)
/*给原子变量做加法并返回:*v 的值增加 i,返回*v 的新值*/
atomic_add_return(i, v)/atomic64_add_return(i, v)/atomic_long_add_return(i, v)
/*给原子变量做减法并返回:*v 的值减少 i,返回*v 的新值*/
atomic_sub_return(i, v)/atomic64_sub_return(i, v)/atomic_long_sub_return(i,v)
/*给原子变量加一并返回:*v 的值加一,返回*v 的新值*/
atomic_inc_return(v)/atomic64_inc_return(v)/ atomic_long_inc_return(v)
/*给原子变量减一并返回:*v 的值减一,返回*v 的新值*/
atomic_dec_return(v)/atomic64_dec_return(v)/atomic_long_dec_return(v)
/*给原子变量做条件加法:如果*v 等于 u 则返回,否则*v 的值增加 i 并返回*v 的新值*/
atomic_add_unless(v, i, u)/atomic_add_unless(v, i, u)/atomic_long_add_unless(v, i, u)
/*给原子变量做条件减法:如果*v 大于 i,则*v -= i 并返回*v 的原值*/
atomic_sub_if_positive(i, v)/atomic64_sub_if_positive(i,v)/atomic_long_sub_if_positive(i, v)
/*原子变量交换:*v 的值更新为 n,返回*v 的原值*/
atomic_xchg(v, n)/atomic64_xchg(v, n)/atomic_long_xchg(v, n)
/*原子变量比较并交换:如果*v 的值等于 o,则*v 的值更新为 n,返回*v 的原值*/
atomic_cmpxchg(v, o, n)/atomic64_cmpxchg(v, o,n)/atomic_long_cmpxchg(v, o, n)
以上相关的代码实现都在内核中的include路径下atomic相关的头文件中以宏定义或者函数的形式实现,这里不再详细的展开介绍。
从 Linux-4.3的内核版本 开始,原子操作引入了各种不同内存有序性的变种版本。以 atomic_set() 为例,现在有四个变种:
(1)严格有序版本:atomic_set(v, i),必须是先执行原子操作以前的访存操作,然后执行原子操作,然后执行原子操作以后的访存操作。相当于原子操作前后都有内存屏障(同时带有 ACQUIRE 和 RELEASE 语义)。
(2)获取有序版本:atomic_set_aquire(v, i),不关心原子操作以前的访存操作,但必须先执行原子操作,然后执行原子操作以后的访存操作。相当于原子操作后面有内存屏障(带有ACQUIRE 语义)。
(3释放有序版本:atomic_set_release(v, i),不关心原子操作以后的访存操作,但必须先执行原子操作以前的访存操作,然后执行执行原子操作。相当于原子操作前面有内存屏障(带有 RELEASE 语义)。
(4)宽松有序版本:atomic_set_relaxed(v, i),完全不关心内存有序性。相当于原子操作前后都没有内存屏障。
(2)本地原子操作
在通用原子变量以外,内核还提供了本地原子变量类型 local_t 和 local64_t。本地原子变量通常是某个 CPU 的本地变量,允许所有的 CPU 读,但是只允许本地 CPU 写。因此本地原子变量不需要内存屏障,性能更好。本地原子变量的主要 API 有:
/*初始化本地原子变量的值为 v*/
LOCAL_INIT(v/LOCAL64_INIT(v)
/*返回本地原子变量*v的值*/
local_read(v)/local64_read(v)
/*给本地原子变量赋值:将*v的值设置成 i*/
local_set(v, i)/local64_set(v, i)
/*给本地原子变量做加法:*v的值增加 i*/
local_add(v, l)/local64_add(v, l)
/*给本地原子变量做减法:*v 的值减少 i*/
local_sub(v, l)/local64_sub(v, l)
/*给本地原子变量做加法并测试:*v 的值增加 i,若结果为负返回 1,否则为 0*/
local_add_negative(v, l)/local64_add_negative(v, l)
/*给本地原子变量做减法并测试:*v 的值减少 i,若结果为 0 返回 1,否则返回 0*/
local_sub_and_test(v, l)/local64_sub_and_test(v, l)
/*给本地原子变量加一:*v 的值加1*/
local_inc(v)/local64_inc(v)
/*给本地原子变量减一:*v 的值减1*/
local_dec(v)/local64_dec(v)
/*给本地原子变量加一并测试:*v 的值加一,若结果为 0 返回 1,否则返回 0*/
local_inc_and_test(v)/local64_inc_and_test(v)
/*给本地原子变量减一并测试:*v 的值减一,若结果为 0 返回 1,否则返回 0*/
local_dec_and_test(v)/local64_dec_and_test(v)
/*给本地原子变量做加法并返回:*v 的值增加 i,返回*v 的新值*/
local_add_return(i, v)/local64_add_return(i, v)
/*给本地原子变量做减法并返回:*v的值减少 i,返回*v 的新值*/
local_sub_return(i, v)/ local64_sub_return(i, v)
/*给本地原子变量加一并返回:*v 的值加一,返回*v 的新值*/
local_inc_return(v)/local64_inc_return(v)
/*给本地原子变量减一并返回:*v的值减一,返回*v 的新值*/
local_dec_return(v)/local64_dec_return(v)
/*本地原子变量交换:*v 的值更新为 n,返回*v 的原值*/
local_xchg(v, n)/local64_xchg(v, n)
/*本地原子变量比较并交换:如果*v 的值等于 0,则*v 的值更新为 n,返回*l 的原值*/
local_cmpxchg(v, 0, n)/local64_cmpxchg(v, 0, n)
以上相关的内核源码都位于include下面的local相关的.h文件中,这里不做详细的介绍了。
(3)位掩码原子操作
在内核中位掩码原子操作是第三类原子操作,可以给定一个长整数类型的位掩码地址,原子性地设置、清除或改变指定位的值。其主要 API 有:
/*返回位掩码*addr 第 nr 位的值*/
int test_bit(int nr, const volatile unsigned long *addr)
/*设置位掩码*addr 第 nr 位的值(设置为 1)*/
void set_bit(unsigned long nr, volatile unsigned long *addr)
/*清除位掩码*addr 第 nr 位的值(设置为 0)*/
void clear_bit(unsigned long nr, volatile unsigned long *addr)
/*转变位掩码*addr 第 nr 位的值(原值为 0 则设 1,原值为 1 则设 0)*/
void change_bit(unsigned long nr, volatile unsigned long *addr)
/*设置位掩码*addr 第 nr 位的值并返回其原值(设置为 1)*/
int test_and_set_bit(unsigned long nr, volatile unsigned long *addr)
/*清除位掩码*addr 第 nr 位的值并返回其原值(设置为 0)*/
int test_and_clear_bit(unsigned long nr, volatile unsigned long *addr)
/*转变位掩码*addr 第 nr 位的值并返回其原值(原值为 0 则设 1,原值为 1 则设 0)*/
int test_and_change_bit(unsigned long nr, volatile unsigned long *addr)
普通原子操作常用于实现引用计数,而位掩码原子操作的主要用途是用来实现 CPU 位掩码 cpumask 和位图 bitmap 的各种操作。
关于原子操作的更多信息可参阅内核文档 Documentation/atomic_ops.txt 和Documentation/local_ops.txt