Android原子操作的实现原理

来源:互联网 发布:web数据挖掘系统 编辑:程序博客网 时间:2024/05/10 22:29

Android原子操作的实现方式和CPU的架构有密切关系,现在的原子操作一般都是在CPU指令级别实现的,这样不但简单,而且效率非常高。下面看看arm平台下Android是如何实现原子操作的。

虽然原子操作的接口函数有十来个,但是实际上只有两个函数中通过汇编代码实现了原子操作:函数android_atomic_addandroid_atomic_cas,其他的函数都是在内部调用它们而已。这两个函数的原理差不多,下面我们以加法为例来了解一下原子变量的实现原理。函数android_atomic_add的代码如下:

externANDROID_ATOMIC_INLINE

int32_tandroid_atomic_add(int32_t increment, volatile int32_t*ptr)

{

   int32_t prev, tmp, status;

   android_memory_barrier();

   do {

       __asm__ __volatile__ ("ldrex %0, [%4]\n"

                             "add %1, %0, %5\n"

                             "strex %2, %1, [%4]"

                             : "=&r" (prev), "=&r" (tmp),

                               "=&r"(status), "+m" (*ptr)

                             : "r" (ptr), "Ir" (increment)

                             : "cc");

   } while (__builtin_expect(status != 0,0));

   return prev;

}  

上面代码中的宏ANDROID_ATOMIC_INLINE的定义是:

#defineANDROID_ATOMIC_INLINE inline__attribute__((always_inline))

实际上就是把函数规定为inline函数。

android_memory_barrier是告诉CPU这里需要内存屏障。下节会介绍内存屏障。

接下来是一段内嵌汇编,这段汇编可以用伪代码来表示:

do{

ldrex prev[ptr]

add  tmp,  prev, increment

strex  status,  tmp,[ptr]

}whiile(status != 0)

add指令的前后有两条看上去比较陌生的指令:ldrexstrex。这两条是AMRV6新引入的同步指令。ldrex指令的作用是把指针ptr指向的内容放到prev变量中,同时给执行处理器做一个标记(tag),标记上指针ptr的地址,表示这个内存地址已经有一个CPU正在访问。当执行到strex指令时,它会检查是否存在ptr的地址标记,如果标记存在,strex指令会把add指令执行的的结果写入指针ptr指向的地址,并且返回0,然后清除该标记。返回的结果0会放在status变量中,这样循环将结束。

如果在strex指令执行前发生了线程的上下文切换,在切换回来后,ldrx指令设置的标志将会被清除。这时再执行strex指令时,由于没有了这个标志,strex指令将不会完成对ptr指针的存储操作,而且status变量中的返回结果是1。这样循环不能结束,重新开始执行,直到成功为止。

__builtin_expectgcc的内建函数,有两个参数,第一个参数是一个表达式,第二个参数是一个值。表达式的计算结果也是函数的结果。__builtin_expect是用来告诉gcc预测表达式更可能的值是什么,这样gcc会根据预测值来优化代码。代码中表达的含义是预测“status!=0”这个表达式的值为“0”,也就是预测while循环将结束。

 

内嵌汇编可以参考本人的博文:gcc内嵌汇编介绍


 

0 0