Linux原子操作 (Linux atomic operations)

来源:互联网 发布:如何减腓肠肌 知乎 编辑:程序博客网 时间:2024/05/01 14:19

原子操作的目的

原子操作的目的是实现对多线程共享的变量进行原子性的操作。这些操作包含读取、设置、加、减、交换(xchg)等等。例如线程A和B共享变量V,且V的初始值是1。线程A和B分别进行如下操作:

A               B

V+1           V+1

当CPU实现加一操作时,有可能先从内存读取V的值,在CPU中加一,再写回内存。这样的三步操作有可能和另一个CPU的操作交织在一起。例如经过下面的操作序列,本来的目的是A,B分别对V加一,结果最终V的值变成了2而非3。由于线程调度的原因,同样的问题对于单CPU的情况一样可能出现。

AV->registerregister=1BV->registerregister=1Aregister++register=2Bregister++register=2Aregister->VV=2Bregister->VV=2

原子操作与memory barrier

原子操作仅仅保证原子性,它并不能保证不同指令的执行顺序。例如对于下面的两行语句,CPU完全可以调换顺序执行。
a=1b=2
要保证指令的执行顺序,需要使用memory barrier。关于barrier的详细介绍可以参见Documentation/memory-barriers.txt。在Linux中,部分原子操作API封装了memory barrier,因此能够保证执行顺序。下面的详细介绍将描述哪些API包含了barrier。

Linux中的整数原子操作函数

由于原子操作的实现是与CPU的类型相关的,所以具体实现有所不同。下面以X86平台为例。

所有的原子操作都作用于类型为atomic_t的变量。在X86平台上它被简单的定义为一个包含一个int类型的结构体。定义成结构体的好处是防止错误的强制转换,防止绕过原子操作API去操作原子变量。
typedef struct {        int counter;} atomic_t;

原子变量初始化

使用下面的宏定义初始原子变量的值。
atomic_t a = ATOMIC_INIT(i);

简单设置和读取原子变量

需要注意的是下面的API均不包含memory barrier。因此使用者需要自己在调用前后添加memory barrier。
atomic_set(&a, i);atomic_read(&a);

简单加减原子变量

根据文档下面的API均不包含memory barrier。但是X86的实现中均使用lock前缀因此具有memory barrier。
atomic_add(i, &a);atomic_sub(i, &a);atomic_inc(&a);atomic_dec(&a);
Linux提供了下面的4个API用于给上面的操作提供memory barrier。不直接使用smp_mb的目的应该是减少系统开销。例如对于X86平台,由于上面的API都已经具有memory barrier,因此下面的函数都简化为了barrier()。
smp_mb__{before,after}_atomic_{dec,int}()

加减原子变量并返回更新后的值

下面的API均包含memory barrier。作为一个general rule,所有具有返回值的原子操作(除了atomic_read)都具有memory barrier。
atomic_add_return(i, &a);atomic_sub_return(i, &a);atomic_inc_return(&a);atomic_dec_return(&a);

加减原子变量并测试是否等于零

下面的API均包含memory barrier。
atomic_add_and_test(i, &a);atomic_sub_and_test(i, &a);atomic_inc_and_test(&a);atomic_dec_and_test(&a);atomic_add_negative(i, &a); // return true if the result is negative

Exchange

下面的API均包含memory barrier。
atomic_xchg(&a, new);// return the old valueatomic_cmpxchg(&a, old, new);  // return the old value


Linux中的位操作原子函数

与整数原子操作函数作用于atomic_t类型不同,位操作的函数作用于无符号整数unsigned long中的某一个bit。

简单设置、清除、取反某一位

与类似的整数原子操作(atomic_add等)相同,以下API不包含memory barrier。但是X86的实现使用了lock前缀,因此具有memory barrier。
        void set_bit(unsigned long nr, volatile unsigned long *addr);        void clear_bit(unsigned long nr, volatile unsigned long *addr);        void change_bit(unsigned long nr, volatile unsigned long *addr);

设置、清除、取反某一位并返回之前的值

与整数原子操作类似,由于具有返回值,因此以下API包含memory barrier。
        int test_and_set_bit(unsigned long nr, volatile unsigned long *addr);        int test_and_clear_bit(unsigned long nr, volatile unsigned long *addr);        int test_and_change_bit(unsigned long nr, volatile unsigned long *addr);

读取某一位

以下API不包含memory barrier。
        int test_bit(unsigned long nr, __const__ volatile unsigned long *addr);

非原子性的位操作

以上的原子位操作基本都有相对应的非原子性操作。这些操作也不具有memory barrier。当变量已经被锁保护的情况下,可以使用这些API。
        void __set_bit(unsigned long nr, volatile unsigned long *addr);        void __clear_bit(unsigned long nr, volatile unsigned long *addr);        void __change_bit(unsigned long nr, volatile unsigned long *addr);        int __test_and_set_bit(unsigned long nr, volatile unsigned long *addr);        int __test_and_clear_bit(unsigned long nr, volatile unsigned long *addr);        int __test_and_change_bit(unsigned long nr, volatile unsigned long *addr);
0 0
原创粉丝点击