分类: LINUX
2013-11-01 23:42:58
原子变量原理分析
内核中同步机制有很多,其中原子变量和自旋锁机制是使用的很普遍的两种机制。周末心情愉快,借这样的时间写一篇分析的文章,也不完全荒废这美好的时间。这种无聊的屌似情调也只能用“呵呵”二字表达了。
闲话少扯,直入主题。话说这原子操作为何物,直白的说就是atomic_read,atomic_write。顾名思义,原子操作就是,对变量的操作是一个整体而不可以打断的。需要这样的操作的最直接原因是,在多核系统中,对内存中变量的操作往往不是一个整体。比如,对内存的写操作往往是这样的过程:
如果两个cpu同时执行同一个变量的自增操作,那么根据上面这个过程来分析,最后得到的变量只会被增加1而不是增加2,从而可以看出原子操作在多核系统上的重要性。
下面我们以atomic_add作为一个例子,来分析arm体系架构下原子加是如何实现的。
static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long tmp;
int result;
__asm__ __volatile__("@ atomic_add_return\n"
"1: ldrex %0, [%2]\n"
" add %0, %0, %3\n"
" strex %1, %0, [%2]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp)
: "r" (&v->counter), "Ir" (i)
: "cc");
return result;
}
首先%0 代表变量result,%1代表tmp,%2代表(&v->counter),%3代表i。而正在实现原子操作的两条指令是ldrex和strex
ldrex %0, [%2]
这条指令为读取&v->counter地址的内容到寄存器result
add %0, %0, %3
这条指令为将寄存器result和i相加,将结果保存到result中
strex %1, %0, [%2]
这条指令是将result寄存器中的值写道&v->counter地址内存的位置
bne 1b
这个指令比较关键,如果上面strex执行成功了,%1将被设置为0,则设置成功,处理结束了,如果%1被设置成为了1,表示设置失败,然后惨烈的重新跳到1:位置重新开始上面的过程。
ldrex和strex是ld/st操作的升级版本。ldrex从内存中读取变量的时候,对于读取地址做了标记,并记录了读取该地址的cpuid。如果在该cpu调用strex之前,有其它核调对同一个地址调用了ldrex,那么这个核在反存时执行strex发现记录的cpuid不对,就会设置操作失效。
同样,如果单个cpu中,ldrex操作被其它ldrex操作打断,同样会导致整个操作失败。原因是strex操作会将上次记录的地址和cpuid清除掉。
对于单核的系统,原子操作则很好实现,只需要简单的关闭中断,操作就不会被打断。
static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags);
val = v->counter;
v->counter = val += i;
raw_local_irq_restore(flags);
return val;
}