内核同步方法-原子操作

原子操作

原子操作用于执行轻量级、仅执行一次的操作,比如修改计数器,某些条件下的增加值或设置位等。原子操作指的是指令以原子的方式执行。之所以用原子来 修饰这种操作方式是因为它们均不可再分。也就是说,原子操作要么一次性执行完毕,要么就不执行,这些操作的执行过程是不可被打断的。原子操作的具体实现取决于体系架构,以下代码如无特别声明,均来自linux/arch/x86/include/asm/atomic.h中

内核中提供了两组原子操作的接口:原子整形操作和原子位操作。前者是一组对整形数据的操作,后者则是针对单独的位操作。通过上面的叙述我们可以知道,这写操作接口完成的动作都是原子的。

原子整形操作

0.数据结构

在linux/include/linux/types.h中有如下定义:

1 190typedef struct {
2 191        int counter;
3 192} atomic_t;

原子类型内部只有一个整型的成员counter。

1.初始化/设置原子变量的值

1 15#define ATOMIC_INIT(i)  { (i) }
2  
3 35static inline void atomic_set(atomic_t *v, int i)
4 36{
5 37        v->counter = i;
6 38}

初始化宏的源码很明显的说明了如何初始化一个原子变量。我们在定义一个原子变量时可以这样的使用它:

1 atomic_t v=ATOMIC_INIT(0);

atomic_set函数可以原子的设置一个变量的值。

2.获取原子变量的值

1 23static inline int atomic_read( const atomic_t *v)
2 24{
3 25        return (*( volatile int *)&(v)->counter);
4 26}

返回原子型变量v中的counter值。关键字volatile保证&(v->counter)的值固定不变,以确保这个函数每次读入的都是原始地址中的值。

3.原子变量的加与减

01 47static inline void atomic_add( int i, atomic_t *v)
02 48{
03 49        asm volatile (LOCK_PREFIX "addl %1,%0"
04 50                     : "+m" (v->counter)
05 51                     : "ir" (i));
06 52}
07  
08 61static inline void atomic_sub( int i, atomic_t *v)
09 62{
10 63        asm volatile (LOCK_PREFIX "subl %1,%0"
11 64                     : "+m" (v->counter)
12 65                     : "ir" (i));
13 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.原子变量的自加自减

01 93static inline void atomic_inc(atomic_t *v)
02   94{
03   95        asm volatile (LOCK_PREFIX "incl %0"
04   96                     : "+m" (v->counter));
05   97}
06  
07 105static inline void atomic_dec(atomic_t *v)
08 106{
09 107        asm volatile (LOCK_PREFIX "decl %0"
10 108                     : "+m" (v->counter));
11 109}

从内联的汇编语句中可以看到,自加自减均采用单条汇编指令,直接在输出数%0上加1或减1。这里的输出数%0的值即是v->counter变量的值。

5.操作并测试

atomic_sub_and_test函数实现的功能是原子的从v减去i,如果结果等于0,则返回1;否则返回0。

1 77static inline int atomic_sub_and_test( int i, atomic_t *v)
2   78{
3   79        unsigned char c;
4   80
5   81        asm volatile (LOCK_PREFIX "subl %2,%0; sete %1"
6   82                     : "+m" (v->counter), "=qm" (c)
7   83                     : "ir" (i) : "memory" );
8   84        return c;
9   85}

通过上述源码可以看到,第一条指令实现减操作;接着根据ZF的值设置c变量的值。sete命令的作用是,如果ZF=1,也就是说减的结果为0,将设置c变量为1;否则c的值为0。执行完毕后返回相应的c值。

01 119static inline int atomic_dec_and_test(atomic_t *v)
02 120{
03 121        unsigned char c;
04 122
05 123        asm volatile (LOCK_PREFIX "decl %0; sete %1"
06 124                     : "+m" (v->counter), "=qm" (c)
07 125                     : : "memory" );
08 126        return c != 0;
09 127}
10  
11 137static inline int atomic_inc_and_test(atomic_t *v)
12 138{
13 139        unsigned char c;
14 140
15 141        asm volatile (LOCK_PREFIX "incl %0; sete %1"
16 142                     : "+m" (v->counter), "=qm" (c)
17 143                     : : "memory" );
18 144        return c != 0;
19 145}

上数两个函数的作用是自减1或自加1后,再测试v值是否为0,如果是0,则返回1;否则返回0。

也许你会认为上述的加/减并测试并不是原子的。我个人的认为是:第一条加/减操作的确是原子的,当此条语句执行完毕后,会产生相应的ZF值。如果此 时在执行sete之前产生了中断,那么肯定会保存当前寄存器等值。因此,即便中间产生了中断,返回的也是中断前加/减后的结果。

6.操作并返回

01 173static inline int atomic_add_return( int i, atomic_t *v)
02 174{
03 175        int __i;
04 176#ifdef CONFIG_M386
05 177        unsigned long flags;
06 178        if (unlikely(boot_cpu_data.x86 <= 3))
07 179                goto no_xadd;
08 180#endif
09 181        /* Modern 486+ processor */
10 182        __i = i;
11 183        asm volatile (LOCK_PREFIX "xaddl %0, %1"
12 184                     : "+r" (i), "+m" (v->counter)
13 185                     : : "memory" );
14 186        return i + __i;
15 187
16 188#ifdef CONFIG_M386
17 189no_xadd: /* Legacy 386 processor */
18 190        raw_local_irq_save(flags);
19 191        __i = atomic_read(v);
20 192        atomic_set(v, i + __i);
21 193        raw_local_irq_restore(flags);
22 194        return i + __i;
23 195#endif
24 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的值时均指初值。

理解了上述过程,再看其他的操作返回函数:

1 205static inline int atomic_sub_return( int i, atomic_t *v)
2 206{
3 207        return atomic_add_return(-i, v);
4 208}

可以看到,atomic_sub_return函数是对atomic_add_return函数的封装。而下面两个函数则又是对上述两个函数的封装。

1 210#define atomic_inc_return(v)  (atomic_add_return(1, v))
2 211#define atomic_dec_return(v)  (atomic_sub_return(1, v))

上述就是对整形原子操作的分析。

转载自:http://edsionte.com/techblog/archives/1809

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值