ARM体系架构下的同步操作
http://blog.hamobai.com/2012/06/28/synchronization-on-ARM-one/
处理器在访问共享资源时,必须对临界区进行同步,即保证同一时间内,只有一个对临界区的访问者。
当共享资源为一内存地址时,原子操作是对该类型共享资源同步访问的最佳方式。
随着应用的日益复杂和SMP的广泛使用,处理器都开始提供硬件同步原语以支持原子地更新内存地址。
CISC处理器比如IA32,可以提供单独的多种原子指令完成复杂的原子操作,由处理器保证读-修改-写回过程的原子性。
而RISC则不同,由于除Load和Store的所有操作都必须在寄存器中完成,
如何保证从装载内存地址到寄存器,到修改寄存器中的值,再到将寄存器中的值写回内存中可以原子性的完成,便成为了处理器设计的关键。
从ARMv6架构开始,ARM处理器提供了Exclusive accesses同步原语,包含两条指令:
LDREX
STREX
LDREX和STREX指令,将对一个内存地址的原子操作拆分成两个步骤,
同处理器内置的记录exclusive accesses的exclusive monitors一起,完成对内存的原子操作。
LDREX
LDREX与LDR指令类似,完成将内存中的数据加载进寄存器的操作。
与LDR指令不同的是,该指令也会同时初始化exclusive monitor来记录对该地址的同步访问。例如
LDREX R1, [R0]
会将R0寄存器中内存地址的数据,加载进R1中并更新exclusive monitor。
STREX
该指令的格式为:
STREX Rd, Rm, [Rn]
STREX会根据exclusive monitor的指示决定是否将寄存器中的值写回内存中。
如果exclusive monitor许可这次写入,则STREX会将寄存器Rm的值写回Rn所存储的内存地址中,并将Rd寄存器设置为0表示操作成功。
如果exclusive monitor禁止这次写入,则STREX指令会将Rd寄存器的值设置为1表示操作失败并放弃这次写入。
应用程序可以根据Rd中的值来判断写回是否成功。
在这篇文章里,首先会以Linux Kernel中ARM架构的原子相加操作为例,介绍这两条指令的使用方法;
之后,会介绍GCC提供的一些内置函数,这些同步函数使用这两条指令完成同步操作。
Linux Kernel中的atomic_add函数
如下是Linux Kernel中使用的atomic_add函数的定义,它实现原子的给 v 指向的atomic_t增加 i 的功能。
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");
}
在第7行,使用LDREX指令将v->counter所指向的内存地址的值装入寄存器中,并初始化exclusive monitor。
在第8行,将该寄存器中的值与i相加。
在第9,10,11行,使用STREX指令尝试将修改后的值存入原来的地址,
如果STREX写入%1寄存器的值为0,则认为原子更新成功,函数返回;
如果%1寄存器的值不为0,则认为exclusive monitor拒绝了本次对内存地址的访问,
则跳转回第7行重新进行以上所述的过程,直到成功将修改后的值写入内存为止。
该过程可能多次反复进行,但可以保证,在最后一次的读-修改-写回的过程中,没有其他代码访问该内存地址。
static inline void atomic_set(atomic_t *v, int i)
{
unsigned long tmp; __asm__ __volatile__("@ atomic_set/n"
"1: ldrex %0, [%1]/n"
" strex %0, %2, [%1]/n"
" teq %0, #0/n"
" bne 1b"
: "=&r" (tmp)
: "r" (&v->counter), "r" (i)
: "cc");
}
输入为v(原子变量),i(要设置的值),均存放在动态分配的寄存器中。tmp用来指示操作是否成功。
GCC内置的原子操作函数
看了上面的GCC内联汇编,是不是有点晕?
在用户态下,GCC为我们提供了一系列内置函数,这些函数可以让我们既享受原子操作的好处,
又免于编写复杂的内联汇编指令。这一系列的函数均以__sync开头,分为如下几类:
type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)
这一系列函数完成对ptr所指向的内存地址的对应操作,并返回操作之前的值。
type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)
这一系列函数完成对ptr所指向的内存地址的对应操作,并返回操作之后的值。
bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)
这两个函数完成对变量的原子比较和交换。
即如果ptr所指向的内存地址存放的值与oldval相同的话,则将其用newval的值替换。
返回bool类型的函数返回比较的结果,相同为true,不同为false;
返回type的函数返回的是ptr指向地址交换前存放的值。
LDREX 和 STREX
独占加载和存储寄存器。
语法
LDREX{cond} Rt, [Rn {, #offset}]
STREX{cond} Rd, Rt, [Rn {, #offset}]
LDREXB{cond} Rt, [Rn]
STREXB{cond} Rd, Rt, [Rn]
LDREXH{cond} Rt, [Rn]
STREXH{cond} Rd, Rt, [Rn]
LDREXD{cond} Rt, Rt2, [Rn]
STREXD{cond} Rd, Rt, Rt2, [Rn]
其中:
cond
是一个可选的条件代码(请参阅条件执行)。
Rd
是存放返回状态的目标寄存器。
Rt
是要加载或存储的寄存器。
Rt2
为进行双字加载或存储时要用到的第二个寄存器。
Rn
是内存地址所基于的寄存器。
offset
为应用于
中的值的可选偏移量。Rn
只可用于 Thumb-2 指令中。 如果省略 offset
,则认为偏移量为 0。offset
LDREX
LDREX
可从内存加载数据。
如果物理地址有共享 TLB 属性,则
LDREX
会将该物理地址标记为由当前处理器独占访问,并且会清除该处理器对其他任何物理地址的任何独占访问标记。否则,会标记:执行处理器已经标记了一个物理地址,但访问尚未完毕。
STREX
STREX
可在一定条件下向内存存储数据。 条件具体如下:
如果物理地址没有共享 TLB 属性,且执行处理器有一个已标记但尚未访问完毕的物理地址,那么将会进行存储,清除该标记,并在
中返回值 0。Rd
如果物理地址没有共享 TLB 属性,且执行处理器也没有已标记但尚未访问完毕的物理地址,那么将不会进行存储,而会在
中返回值 1。Rd
如果物理地址有共享 TLB 属性,且已被标记为由执行处理器独占访问,那么将进行存储,清除该标记,并在
中返回值 0。Rd
如果物理地址有共享 TLB 属性,但没有标记为由执行处理器独占访问,那么不会进行存储,且会在
中返回值 1。Rd
限制
r15 不可用于 Rd
、Rt
、Rt2
或 Rn
中的任何一个。
对于 STREX
,Rd
一定不能与 Rt
、Rt2
或 Rn
为同一寄存器。
对于 ARM 指令:
必须是一个编号为偶数的寄存器,且不能为 r14Rt
必须为Rt2
R(t+1)
不允许使用
。offset
对于 Thumb 指令:
r13 不可用于
、Rd
或Rt
中的任何一个Rt2
对于
LDREXD
,
和Rt
不可为同一个寄存器Rt2
的值可为 0-1020 范围内 4 的任何倍数。offset
用法
利用 LDREX
和 STREX
可在多个处理器和共享内存系统之前实现进程间通信。
出于性能方面的考虑,请将相应 LDREX
指令和 STREX
指令间的指令数控制到最少。
Note
STREX
指令中所用的地址必须要与近期执行次数最多的 LDREX
指令所用的地址相同。
如果使用不同的地址,则 STREX
指令的执行结果将不可预知。
体系结构
ARM LDREX
和 STREX
可用于 ARMv6 及更高版本中。
ARM LDREXB
、LDREXH
、LDREXD
、STREXB
、STREXD
和 STREXH
可用于 ARMv6K 及更高版本中。
所有这些 32 位 Thumb 指令均可用于 ARMv6T2 及更高版本,但 LDREXD
和 STREXD
在 ARMv7-M 架构中不可用。
这些指令均无 16 位版本。
示例
MOV r1, #0x1 ; load the ‘lock taken’ value
try
LDREX r0, [LockAddr] ; load the lock value
CMP r0, # ; is the lock free?
STREXEQ r0, r1, [LockAddr] ; try and claim the lock
CMPEQ r0, # ; did this succeed?
BNE try ; no – try again
.... ; yes – we have the lock
http://lxr.free-electrons.com/source/arch/arm/include/asm/atomic.h?v=2.6.33
/*
* arch/arm/include/asm/atomic.h
*
* Copyright (C) 1996 Russell King.
* Copyright (C) 2002 Deep Blue Solutions Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __ASM_ARM_ATOMIC_H
#define __ASM_ARM_ATOMIC_H #include <linux/compiler.h>
#include <linux/types.h>
#include <asm/system.h> #define ATOMIC_INIT(i) { (i) } #ifdef __KERNEL__ /*
* On ARM, ordinary assignment (str instruction) doesn't clear the local
* strex/ldrex monitor on some implementations. The reason we can use it for
* atomic_set() is the clrex or dummy strex done on every exception return.
*/
#define atomic_read(v) ((v)->counter)
#define atomic_set(v,i) (((v)->counter) = (i)) #if __LINUX_ARM_ARCH__ >= 6 /*
* ARMv6 UP and SMP safe atomic ops. We use load exclusive and store exclusive to ensure that these are atomic.
* We may loop to ensure that the update happens.
*/
static inline void atomic_add(int i, atomic_t *v)
{
unsigned long tmp;
int result; __asm__ __volatile__("@ atomic_add\n"
"1: ldrex %0, [%2]\n"
" add %0, %0, %3\n"
" strex %1, %0, [%2]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp)
: "r" (&v->counter), "Ir" (i)
: "cc");
} static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long tmp;
int result; smp_mb(); __asm__ __volatile__("@ atomic_add_return\n"
"1: ldrex %0, [%2]\n"
" add %0, %0, %3\n"
" strex %1, %0, [%2]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp)
: "r" (&v->counter), "Ir" (i)
: "cc"); smp_mb(); return result;
} static inline void atomic_sub(int i, atomic_t *v)
{
unsigned long tmp;
int result; __asm__ __volatile__("@ atomic_sub\n"
"1: ldrex %0, [%2]\n"
" sub %0, %0, %3\n"
" strex %1, %0, [%2]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp)
: "r" (&v->counter), "Ir" (i)
: "cc");
} static inline int atomic_sub_return(int i, atomic_t *v)
{
unsigned long tmp;
int result; smp_mb(); __asm__ __volatile__("@ atomic_sub_return\n"
"1: ldrex %0, [%2]\n"
" sub %0, %0, %3\n"
" strex %1, %0, [%2]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp)
: "r" (&v->counter), "Ir" (i)
: "cc"); smp_mb(); return result;
} static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new)
{
unsigned long oldval, res; smp_mb(); do {
__asm__ __volatile__("@ atomic_cmpxchg\n"
"ldrex %1, [%2]\n"
"mov %0, #0\n"
"teq %1, %3\n"
"strexeq %0, %4, [%2]\n"
: "=&r" (res), "=&r" (oldval)
: "r" (&ptr->counter), "Ir" (old), "r" (new)
: "cc");
} while (res); smp_mb(); return oldval;
} static inline void atomic_clear_mask(unsigned long mask, unsigned long *addr)
{
unsigned long tmp, tmp2; __asm__ __volatile__("@ atomic_clear_mask\n"
"1: ldrex %0, [%2]\n"
" bic %0, %0, %3\n"
" strex %1, %0, [%2]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (tmp), "=&r" (tmp2)
: "r" (addr), "Ir" (mask)
: "cc");
} #else /* ARM_ARCH_6 */ #ifdef CONFIG_SMP
#error SMP not supported on pre-ARMv6 CPUs
#endif static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long flags;
int val; raw_local_irq_save(flags);
val = v->counter;
v->counter = val += i;
raw_local_irq_restore(flags); return val;
}
#define atomic_add(i, v) (void) atomic_add_return(i, v) static inline int atomic_sub_return(int i, atomic_t *v)
{
unsigned long flags;
int val; raw_local_irq_save(flags);
val = v->counter;
v->counter = val -= i;
raw_local_irq_restore(flags); return val;
}
#define atomic_sub(i, v) (void) atomic_sub_return(i, v) static inline int atomic_cmpxchg(atomic_t *v, int old, int new)
{
int ret;
unsigned long flags; raw_local_irq_save(flags);
ret = v->counter;
if (likely(ret == old))
v->counter = new;
raw_local_irq_restore(flags); return ret;
} static inline void atomic_clear_mask(unsigned long mask, unsigned long *addr)
{
unsigned long flags; raw_local_irq_save(flags);
*addr &= ~mask;
raw_local_irq_restore(flags);
} #endif /* __LINUX_ARM_ARCH__ */ #define atomic_xchg(v, new) (xchg(&((v)->counter), new)) static inline int atomic_add_unless(atomic_t *v, int a, int u)
{
int c, old; c = atomic_read(v);
while (c != u && (old = atomic_cmpxchg((v), c, c + a)) != c)
c = old;
return c != u;
}
#define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0) #define atomic_inc(v) atomic_add(1, v)
#define atomic_dec(v) atomic_sub(1, v) #define atomic_inc_and_test(v) (atomic_add_return(1, v) == 0)
#define atomic_dec_and_test(v) (atomic_sub_return(1, v) == 0)
#define atomic_inc_return(v) (atomic_add_return(1, v))
#define atomic_dec_return(v) (atomic_sub_return(1, v))
#define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0) #define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0) #define smp_mb__before_atomic_dec() smp_mb()
#define smp_mb__after_atomic_dec() smp_mb()
#define smp_mb__before_atomic_inc() smp_mb()
#define smp_mb__after_atomic_inc() smp_mb() #include <asm-generic/atomic-long.h>
#endif
#endif
ARM体系架构下的同步操作的更多相关文章
- [转] 张凌 ARM体系架构
很多时候我们都会对M0,M0+,M3,M4,M7,arm7,arm9,CORTEX-A系列,或者说AVR,51,PIC等,一头雾水,只知道是架构,不知道具体是什么,有哪些不同?今天查了些资料,来解解惑 ...
- 关于cpu体系架构的一些有趣的故事分享
从排查一次匪夷所思的coredump,引出各种体系架构的差异. 本文中的所有内容来自学习DCC888的学习笔记或者自己理解的整理,如需转载请注明出处.周荣华@燧原科技 1 背景 从全世界有记载的第一台 ...
- 说说面向服务的体系架构SOA
序言 在.Net的世界中,一提及SOA,大家想到的应该是Web Service,WCF,还有人或许也会在.NET MVC中的Web API上做上标记,然后泛泛其谈! 的确,微软的这些技术也确实推动着面 ...
- 面向服务的体系架构SOA
面向服务的体系架构SOA 序言 在.Net的世界中,一提及SOA,大家想到的应该是Web Service,WCF,还有人或许也会在.NET MVC中的Web API上做上标记,然后泛泛其谈! 的确,微 ...
- 代码 or 指令,浅析ARM架构下的函数的调用过程
摘要:linux程序运行的状态以及如何推导调用栈. 1.背景知识 1.ARM64寄存器介绍: 2.STP指令详解(ARMV8手册): 我们先看一下指令格式(64bit),以及指令对于寄存机执行结果的影 ...
- ARM架构下的Docker环境,OpenJDK官方没有8版本镜像,如何完美解决?
为什么需要ARM架构下的OpenJDK8的Docker镜像? 对现有的Java应用,之前一直运行在x86处理器环境下,编译和运行都是JDK8,如今在树莓派的Docker环境运行(或者其他ARM架构电脑 ...
- InnoDB体系架构(四)Master Thread工作方式
Master Thread工作方式 在前面的文章:InnoDB体系架构——后台线程 说到:InnoDB存储引擎的主要工作都是在一个单独的后台线程Master Thread中完成.这篇具体介绍该线程的具 ...
- ARM系统架构
ARM系统架构 一.ARM概要 ARM架构,曾称进阶精简指令集机器(Advanced RISC Machine)更早称作Acorn RISC Machine,是一个32位精简指令集(RISC)处理器架 ...
- CI Weekly #5 | 微服务架构下的持续部署与交付
CI Weekly 围绕『 软件工程效率提升』 进行一系列技术内容分享,包括国内外持续集成.持续交付,持续部署.自动化测试. DevOps 等实践教程.工具与资源,以及一些工程师文化相关的程序员 Ti ...
随机推荐
- 使用Nodejs+mongodb开发地图瓦片服务器
原先地图瓦片服务器采用的是arcgisserver发布的地图服务并进行切片,但ags发布的地图服务很占内存,发布太多的话服务器压力很大.再一个就是ags价太高了. 学习Nodejs之后,发现这是一个可 ...
- 使用Jquery Mobile设计Android通讯录
本系列教程将指导大家一步步使用Jquery Mobile设计一个Android的通讯录应用.其中在应用的界面部分,将使用jQuery Mobile框架,并且会指导大家如何使Android中提供的web ...
- C++模板实例掌握
前段时间重新学习C++,主要看C++编程思想和C++设计新思维.对模版的使用有了更进一层的了解,特总结如下: 下面列出了模版的常用情况: << '\n';} //参考:http://ww ...
- Learning Vector
题意: 给出n组x,y增量,从(0,0)开始以x,y坐标增加后等到的终点坐标,可以构成一个面积,再以这个终点为起点再增加,以此类推,使用增量顺序不同,得到的面积不,求用k组增量能得到的最大的面积. 分 ...
- 在刚接触TI-DM8127-ipnc框架时注意的问题
1. 修改内存分配不成功? 解决方法: 修改内存分配后需要重新编译mcfw.它影响3个核. 如果修改了cmem需要修改boostara. 2. 命令make clean后在make相机跑不起来? 解决 ...
- bzoj 2190 [SDOI2008]仪仗队(欧拉函数)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2190 [题意] n*n的正方形,在(0,0)格点可以看到的格子数目. [思路] 预处理 ...
- linq性能剖析
Orcas(VS2008&Framework3.5)给我们带来了很多令人兴奋的新特性,尤其是LINQ的引进,可以说方便了一大批开发 人员和框架设计人员.过去,当我们使用O/RMapping的一 ...
- WS之cxf简单实现
1.服务端实现: 1.1 定义接口,用@WebService修饰: /** @WebService 所修饰的接口,那么接口里面的方法全部都属于web的服务 */ @WebService public ...
- Java基础 —— Java常用类
Java常用类: java.lang包: java.lang.Object类: hashcode()方法:返回一段整型的哈希码,代表地址. toString()方法:返回父类名+"@&quo ...
- html5 canvas 像素随机百分之十显示
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...