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内嵌汇编介绍
虽然原子操作的接口函数有十来个,但是实际上只有两个函数中通过汇编代码实现了原子操作:函数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内嵌汇编介绍
相关文章推荐
- Android原子操作的实现原理
- Java实现原子操作的原理
- 原子操作的实现原理详解
- 原子操作的实现原理
- 聊聊并发(五)——原子操作的实现原理
- 原子操作的实现原理
- java多线程-专题-聊聊并发(五)原子操作的实现原理
- Java 并发 ---原子操作的实现原理
- 原子操作的实现原理
- 聊聊并发(五)原子操作的实现原理
- 聊聊并发(五)——原子操作的实现原理
- 原子操作的实现与原理
- 聊聊并发(5):原子操作的实现原理
- 原子操作的实现原理
- 个人学习笔记---linux原子操作的实现原理
- Linux 互斥锁、原子操作实现原理
- Java并发机制底层——原子操作的实现原理
- 深入分析Volatile的实现原理,原子操作
- java并发编程学习(三) 原子操作的实现原理及java中如何实现原子操作
- java原子操作实现原理