linux原子操作

来源:互联网 发布:tomcat运行php文件 编辑:程序博客网 时间:2024/05/29 09:12

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



一 简介

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



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



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


假设count变量初始值为0。进程1执行完“mov eax, [count]”后,寄存器eax内保存了count的值0。此时,进程2被调度执行,抢占了进程1CPU的控制权。进程2执行“count++;”的汇编代码,将累加后的count1写回到内存。然后,进程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(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""bne1b": "=&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);


加,减,读,测试

关于原子变量的主要运算操作,参考上表,语义较明显,不作过多阐述。









0 0
原创粉丝点击