您的位置:首页 > 运维架构 > Linux

linux原子操作

2015-09-14 15:29 1121 查看


以下内容有些参考自网络,自然也将其学习所得的一点知识奉还给网络。


一 简介

linux文档中比较权威齐全的介绍位于Documentation/atomic_ops.txt,里面介绍的比较仔细全面,原子操作的由来主要为了解决现代操作系统多任务,多处理器并发的发展中资源竞争导致的同步问题,具体场景举例如下:

例如C语言语句“count++;”在未经编译器优化时生成的汇编代码为。



当操作系统内存在多个进程同时执行这段代码时,就可能带来并发问题。



假设count变量初始值为0。进程1执行完“mov eax, [count]”后,寄存器eax内保存了count的值0。此时,进程2被调度执行,抢占了进程1的CPU的控制权。进程2执行“count++;”的汇编代码,将累加后的count值1写回到内存。然后,进程1再次被调度执行,CPU控制权回到进程1。进程1接着执行,计算count的累加值仍为1,写回到内存。虽然进程1和进程2执行了两次“count++;”操作,但是count实际的内存值为1,而不是2!

那么如何解决这个问题呢?不同处理器架构对此都有不同的实现方法!!

二 实现

内核提供了一个特殊的类型atomic_t,具体定义如下:

typedef struct { 
    int counter; 
} atomic_t;

从上面的定义来看,atomic_t实际上就是一个int类型的counter,不过定义这样特殊的类型atomic_t是有其思考的:内核定义了若干atomic_xxx的接口API函数,这些函数只会接收atomic_t类型的参数。这样可以确保atomic_xxx的接口函数只会操作atomic_t类型的数据。同样的,如果你定义了atomic_t类型的变量(你期望用atomic_xxx的接口API函数操作它),这些变量也不会被那些普通的、非原子变量操作的API函数(如初始化时用memset)接受。

具体的接口API函数整理如下:
接口函数

描述

static inline void atomic_add(int i, atomic_t *v)

给一个原子变量v增加i

static inline int atomic_add_return(int i, atomic_t *v)

同上,只不过将变量v的最新值返回

static inline void atomic_sub(int i, atomic_t *v)

给一个原子变量v减去i

static inline int atomic_sub_return(int i, atomic_t *v)

同上,只不过将变量v的最新值返回

static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new)

比较old和原子变量ptr中的值,如果相等,那么就把new值赋给原子变量。 
返回旧的原子变量ptr中的值

atomic_read

获取原子变量的值

atomic_set

设定原子变量的值

atomic_inc(v)

原子变量的值加一

atomic_inc_return(v)

同上,只不过将变量v的最新值返回

atomic_dec(v)

原子变量的值减去一

atomic_dec_return(
a666
v)

同上,只不过将变量v的最新值返回

atomic_sub_and_test(i, v)

给一个原子变量v减去i,并判断变量v的最新值是否等于0

atomic_add_negative(i,v)

给一个原子变量v增加i,并判断变量v的最新值是否是负数

static inline int atomic_add_unless(atomic_t *v, int a, int u)

只要原子变量v不等于u,那么就执行原子变量v加a的操作。 
如果v不等于u,返回非0值,否则返回0值

X86原子操作实现

比如atomic_inc API具体实现,其他类似:
static inline void atomic_inc(atomic_t *v)
{
asm volatile(LOCK_PREFIX "incl %0"
: "+m" (v->counter));
}


其中LOCK_PREFIX宏的定义为
#ifdef CONFIG_SMP
#define LOCK_PREFIX_HERE \
".pushsection .smp_locks,\"a\"\n"	\
".balign 4\n"				\
".long 671f - .\n" /* offset */		\
".popsection\n"				\
"671:"

#define LOCK_PREFIX LOCK_PREFIX_HERE "\n\tlock; "

#else /* ! CONFIG_SMP */
#define LOCK_PREFIX_HERE ""
#define LOCK_PREFIX ""
#endif

可见,在对称多处理器架构的情况下,LOCK_PREFIX被解释为指令前缀lock。而对于单处理器架构,LOCK_PREFIX不包含任何内容.


Arm原子操作实现

在arm下(linux-3.10.x),atomic_inc展开为atomic_add(1, v),在armv6以上(armV6之前的不讨论,一般是屏蔽中断实现)的具体实现如下:
static inline void atomic_add(int i, atomic_t *v)
{
unsigned long tmp;
int result;

__asm__ __volatile__("@ atomic_add\n"
"1: ldrex %0, [%3]\n"
" add %0, %0, %4\n"
" strex %1, %0, [%3]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
: "r" (&v->counter), "Ir" (i)
: "cc");
}
关于x86和arm架构的具体实现可以参考以下链接:

http://www.wowotech.net/kernel_synchronization/atomic.html


http://www.cnblogs.com/fanzhidongyzby/p/3654855.html

三 应用

初始化

初始化分为静态初始化和动态初始化两种。静态初始化类似:

static atomic_t probe_count = ATOMIC_INIT(0);

动态初始化则调用atomic_set接口进行变量的显示赋值:

atomic_set(&q->depth, 0);

加,减,读,测试

关于原子变量的主要运算操作,参考上表,语义较明显,不作过多阐述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 原子操作