Latest Update:2010/10/23
原子操作
原子操作用于执行轻量级、仅执行一次的操作,比如修改计数器,某些条件下的增加值或设置位等。原子操作指的是指令以原子的方式执行。之所以用原子来修饰这种操作方式是因为它们均不可再分。也就是说,原子操作要么一次性执行完毕,要么就不执行,这些操作的执行过程是不可被打断的。原子操作的具体实现取决于体系架构,以下代码如无特别声明,均来自linux/arch/x86/include/asm/atomic.h中
内核中提供了两组原子操作的接口:原子整形操作和原子位操作。前者是一组对整形数据的操作,后者则是针对单独的位操作。通过上面的叙述我们可以知道,这写操作接口完成的动作都是原子的。
原子整形操作
0.数据结构
在linux/include/linux/types.h中有如下定义:
190typedef struct { 191 int counter; 192} atomic_t;
原子类型内部只有一个整型的成员counter。
1.初始化/设置原子变量的值
15#define ATOMIC_INIT(i) { (i) } 35static inline void atomic_set(atomic_t *v, int i) 36{ 37 v->counter = i; 38}
初始化宏的源码很明显的说明了如何初始化一个原子变量。我们在定义一个原子变量时可以这样的使用它:
atomic_t v=ATOMIC_INIT(0);
atomic_set函数可以原子的设置一个变量的值。
2.获取原子变量的值
23static inline int atomic_read(const atomic_t *v) 24{ 25 return (*(volatile int *)&(v)->counter); 26}
返回原子型变量v中的counter值。关键字volatile保证&(v->counter)的值固定不变,以确保这个函数每次读入的都是原始地址中的值。
3.原子变量的加与减
47static inline void atomic_add(int i, atomic_t *v) 48{ 49 asm volatile(LOCK_PREFIX "addl %1,%0" 50 : "+m" (v->counter) 51 : "ir" (i)); 52} 61static inline void atomic_sub(int i, atomic_t *v) 62{ 63 asm volatile(LOCK_PREFIX "subl %1,%0" 64 : "+m" (v->counter) 65 : "ir" (i)); 66}
加减操作中使用了内联汇编语句。
linux中的汇编语句都采用AT&T指令格式。带有C表达式的内联汇编格式为:__asm__ __volatile__(“InstructionList” :Output :Input :Clobber/Modify);其中asm(或__asm__)用来声明一个内联汇编语句;关键字volatile是可选的,选择此关键字意味着向编译器声明“不要动我所写的指令,我需要原封不动的保留每一条指令”,否则,编译器可能会对指令进行优化。InstructionList即我们所加的汇编语句。指令后面的3个部分(Output, Input,Clobber/Modify)是可选的,分别指输出操作数,输入操作数,修改位。
下面我们针对上述加减操作对上述内联汇编语句做简单的解释。
在加函数中,对变量加操作的原子型表现在使用单条的汇编语句完成。指令中的%1,%0分别代表输入操作数和输出操作数,它们用来标示变量。内联汇编语句中的操作数从0开始进行编号,并且优先为输出操作数编号,然后再依次为输入操作数编号。在指令后的输出部分,“+m”中的+表示输出变量是可读可写的,m表示输出操作数存储于内存单元中,而不是在寄存器中。在输入部分,“ir”中的i表示输入操作数是一个直接操作数,r表示存储与寄存器中。输入部分和输出部分中的括号内的C语言格式的变量分别对应汇编语句中的输入操作数和输出操作数。
汇编语句addl %1,%0就是将i加至v->counter上,这个加的过程是原子的。
了解了加操作,那么减操作也就不难理解了,这里不再赘述。
4.原子变量的自加自减
93static inline void atomic_inc(atomic_t *v) 94{ 95 asm volatile(LOCK_PREFIX "incl %0" 96 : "+m" (v->counter)); 97} 105static inline void atomic_dec(atomic_t *v) 106{ 107 asm volatile(LOCK_PREFIX "decl %0" 108 : "+m" (v->counter)); 109}
从内联的汇编语句中可以看到,自加自减均采用单条汇编指令,直接在输出数%0上加1或减1。这里的输出数%0的值即是v->counter变量的值。
5.操作并测试
atomic_sub_and_test函数实现的功能是原子的从v减去i,如果结果等于0,则返回1;否则返回0。
77static inline int atomic_sub_and_test(int i, atomic_t *v) 78{ 79 unsigned char c; 80 81 asm volatile(LOCK_PREFIX "subl %2,%0; sete %1" 82 : "+m" (v->counter), "=qm" (c) 83 : "ir" (i) : "memory"); 84 return c; 85}
通过上述源码可以看到,第一条指令实现减操作;接着根据ZF的值设置c变量的值。sete命令的作用是,如果ZF=1,也就是说减的结果为0,将设置c变量为1;否则c的值为0。执行完毕后返回相应的c值。
119static inline int atomic_dec_and_test(atomic_t *v) 120{ 121 unsigned char c; 122 123 asm volatile(LOCK_PREFIX "decl %0; sete %1" 124 : "+m" (v->counter), "=qm" (c) 125 : : "memory"); 126 return c != 0; 127} 137static inline int atomic_inc_and_test(atomic_t *v) 138{ 139 unsigned char c; 140 141 asm volatile(LOCK_PREFIX "incl %0; sete %1" 142 : "+m" (v->counter), "=qm" (c) 143 : : "memory"); 144 return c != 0; 145}
上数两个函数的作用是自减1或自加1后,再测试v值是否为0,如果是0,则返回1;否则返回0。
也许你会认为上述的加/减并测试并不是原子的。我个人的认为是:第一条加/减操作的确是原子的,当此条语句执行完毕后,会产生相应的ZF值。如果此时在执行sete之前产生了中断,那么肯定会保存当前寄存器等值。因此,即便中间产生了中断,返回的也是中断前加/减后的结果。
6.操作并返回
173static inline int atomic_add_return(int i, atomic_t *v) 174{ 175 int __i; 176#ifdef CONFIG_M386 177 unsigned long flags; 178 if (unlikely(boot_cpu_data.x86 <= 3)) 179 goto no_xadd; 180#endif 181 /* Modern 486+ processor */ 182 __i = i; 183 asm volatile(LOCK_PREFIX "xaddl %0, %1" 184 : "+r" (i), "+m" (v->counter) 185 : : "memory"); 186 return i + __i; 187 188#ifdef CONFIG_M386 189no_xadd: /* Legacy 386 processor */ 190 raw_local_irq_save(flags); 191 __i = atomic_read(v); 192 atomic_set(v, i + __i); 193 raw_local_irq_restore(flags); 194 return i + __i; 195#endif 196}
首先if判断语句判断当前的处理器是否为386或386以下,如果是则直接执行no_xadd处开始的代码;否则紧接着判断语句执行。此处的unlikely表示”经常不“。因为现在的处理器大都高于386,所以编译器一般都按照高于386来处理,以达到优化源代码的目的。搞清楚这段代码的逻辑关系,那么接下来的工作就不困难了。
如果CPU高于386,则它将执行xaddl命令,该命令的作用是先交换源和目的操作数,再相加。这里%0,%1分别代表i和v->counter。首先将i值用__i保存;操作数交换后,%0,%1的值依次为v->counter和i;相加并保存到%1;最后%0,%1的结果就是v->counter,i+v->counter,返回的结果i+__i的结果就是i+v->counter。这里涉及到i和v->counter的值时均指初值。
理解了上述过程,再看其他的操作返回函数:
205static inline int atomic_sub_return(int i, atomic_t *v) 206{ 207 return atomic_add_return(-i, v); 208}
可以看到,atomic_sub_return函数是对atomic_add_return函数的封装。而下面两个函数则又是对上述两个函数的封装。
210#define atomic_inc_return(v) (atomic_add_return(1, v)) 211#define atomic_dec_return(v) (atomic_sub_return(1, v))
上述就是对整形原子操作的分析。