您的位置:首页 > 移动开发 > Android开发

Android原子操作的实现原理

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

虽然原子操作的接口函数有十来个,但是实际上只有两个函数中通过汇编代码实现了原子操作:函数android_atomic_add和android_atomic_cas,其他的函数都是在内部调用它们而已。这两个函数的原理差不多,下面我们以加法为例来了解一下原子变量的实现原理。函数android_atomic_add的代码如下:
extern
ANDROID_ATOMIC_INLINE
int32_t
android_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的定义是:
#define
ANDROID_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指令的前后有两条看上去比较陌生的指令:ldrex和strex。这两条是AMRV6新引入的同步指令。ldrex指令的作用是把指针ptr指向的内容放到prev变量中,同时给执行处理器做一个标记(tag),标记上指针ptr的地址,表示这个内存地址已经有一个CPU正在访问。当执行到strex指令时,它会检查是否存在ptr的地址标记,如果标记存在,strex指令会把add指令执行的的结果写入指针ptr指向的地址,并且返回0,然后清除该标记。返回的结果0会放在status变量中,这样循环将结束。

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

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

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: