Linux互斥与同步之原子操作

例子

一个全局共享的变量flag

int flag=0

进程A

void funcA()
{
    flag++;
}

进程B

void funcB()
{
    flag++
}

在进程A和进程B都运行起来后,flag的值应该会是多少?

case1

funcA先执行,再执行funcB。或者 funcB先执行,再执行funcA。
上述无论那个先执行,结果都是2。没有什么多说的。

case2

先说下flag++在汇编级别的代码:

P1. mov %eax $flag   //将flag保存到寄存器中
P2. inc %eax         //将寄存器中的值加1
P3. mov $flag %eax   //将寄存器中的值回写到flag中

如果funcA先执行,当执行完P1指令后,没有执行P2指令时。funcB被处理器调度执行,当funcB从头到尾执行完后,flag等于1。然后回到funcA打断的节点上,继续执行。因为以前执行了P1,导致eax中的值依然是0,所以funcA执行完后,flag还是为1。

对于单CPU当执行系统调用的时候有中断发生,也会出现上述情况。
对于多CPU,如果不是percpu变量,多个CPU共享的,也会出现多个CPU修改同一个值的现象。

针对上述情况,kernel就针对保护一个整型变量提出了原子变量。

原子变量

Linux源码中定义了一个类型为atomic_t的原子变量。

typedef struct{
    int counter;
}atomic_t;

#define CONFIG_64BIT
typedef struct{
    long counter;
}atomic64_t;
#endif

从上述定义可以看出,在32位上atomic_t就是一个init型counter, 在64位系统上使用的是long型变量。

原子操作函数集

接口函数 详细说明
atomic_add(int i, atomic_t *v) 给原子变量v加i
atomic_sub(int i, atomic_t *v) 给原子变量v减i
atomic_inc(atomic_t *v) 原子变量v加1
atomic_dec(atomic_t *v) 原子变量v减1
atomic_add_return(int i, atomic_t *v) 给原子变量v加i,并将最新的v返回
atomic_sub_return(int i, atomic_t *v) 给原子变量v减i,并将最新的v返回
atomic_read(v) 获得原子变量的值
atomic_set(v, i) 给原子变量v设置为i
atomic_cmpxchg(v, old, new) 比较old和v的值是否相等,如果相等,就把new赋值给v
__atomic_add_unless(v, a, u) 如果u不等与c,就将v+a复制给v

以上是atomic_t绝大多数的原子操作函数集合。这些函数实现都依赖于特定的硬件平台。

x86上atomic_add实现

 /**
  * atomic_add - add integer to atomic variable
  * @i: integer value to add
  * @v: pointer of type atomic_t
  *
  * Atomically adds @i to @v.
  */
 static inline void atomic_add(int i, atomic_t *v)
 {
     asm volatile(LOCK_PREFIX "addl %1,%0"
              : "+m" (v->counter)
              : "ir" (i));
 }

x86上用带有“LOCK_PREFIX前缀的addl指令来保证原子变量v加1操作的原子性。”LOCK_PREFIX“前缀在x86上的作用在执行add指令时独占系统总线。这样在执行期间,别的进程也无法修改counter的值

ARM上atomic_add_resturn实现

static inline int atomic_add_return(int i, atomic_t *v)      \
{                                   \
     unsigned long tmp;                      \
     int result;                         \
                                    \
     asm volatile("// atomic_add_return\n"           \
 "1: ldxr    %w0, %2\n"                      \
 "   add     %w0, %w0, %w3\n"                \
 "   stlxr   %w1, %w0, %2\n"                     \
 "   cbnz    %w1, 1b"                        \
     : "=&r" (result), "=&r" (tmp), "+Q" (v->counter)        \
     : "Ir" (i)                          \
     : "memory");                            \
                                     \
     smp_mb();                           \
     return result;                          \
 }
汇编语言 C语言
1: ldxr %w0, %2 result = v->counter;
add %w0, %w0, %w3 result = result + i;
stlxr %w1, %w0, %2 v->counter = result; tmp = 设置是否成功;
cnnz %w1, 1b if(tmp != 0) goto 1;

再看开始的例子

一个全局共享的变量flag

atomic_t flag=ATOMIC_INIT(0);

进程A

void funcA()
{
    atomic_inc(&flag);
}

进程B

void funcB()
{
    atomic_inc(&flag);
}

这样之后,无论A或者B谁先执行,结果都是2。

猜你喜欢

转载自blog.csdn.net/longwang155069/article/details/51917113