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 中的值的可选偏移量。offset 只可用于 Thumb-2 指令中。 如果省略 offset,则认为偏移量为 0。

LDREX

LDREX 可从内存加载数据。

  • 如果物理地址有共享 TLB 属性,则 LDREX 会将该物理地址标记为由当前处理器独占访问,并且会清除该处理器对其他任何物理地址的任何独占访问标记。

  • 否则,会标记:执行处理器已经标记了一个物理地址,但访问尚未完毕。

STREX

STREX 可在一定条件下向内存存储数据。 条件具体如下:

  • 如果物理地址没有共享 TLB 属性,且执行处理器有一个已标记但尚未访问完毕的物理地址,那么将会进行存储,清除该标记,并在 Rd 中返回值 0。

  • 如果物理地址没有共享 TLB 属性,且执行处理器也没有已标记但尚未访问完毕的物理地址,那么将不会进行存储,而会在 Rd 中返回值 1。

  • 如果物理地址有共享 TLB 属性,且已被标记为由执行处理器独占访问,那么将进行存储,清除该标记,并在 Rd 中返回值 0。

  • 如果物理地址有共享 TLB 属性,但没有标记为由执行处理器独占访问,那么不会进行存储,且会在 Rd中返回值 1。

限制

r15 不可用于 RdRtRt2 或 Rn 中的任何一个。

对于 STREXRd 一定不能与 RtRt2 或 Rn 为同一寄存器。

对于 ARM 指令:

  • Rt 必须是一个编号为偶数的寄存器,且不能为 r14

  • Rt2 必须为 R(t+1)

  • 不允许使用 offset

对于 Thumb 指令:

  • r13 不可用于 RdRt 或 Rt2 中的任何一个

  • 对于 LDREXDRt 和 Rt2 不可为同一个寄存器

  • offset 的值可为 0-1020 范围内 4 的任何倍数。

用法

利用 LDREX 和 STREX 可在多个处理器和共享内存系统之前实现进程间通信。

出于性能方面的考虑,请将相应 LDREX 指令和 STREX 指令间的指令数控制到最少。

Note

STREX 指令中所用的地址必须要与近期执行次数最多的 LDREX 指令所用的地址相同。
如果使用不同的地址,则 STREX 指令的执行结果将不可预知。

体系结构

ARM LDREX 和 STREX 可用于 ARMv6 及更高版本中。

ARM LDREXBLDREXHLDREXDSTREXBSTREXD 和 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体系架构下的同步操作的更多相关文章

  1. [转] 张凌 ARM体系架构

    很多时候我们都会对M0,M0+,M3,M4,M7,arm7,arm9,CORTEX-A系列,或者说AVR,51,PIC等,一头雾水,只知道是架构,不知道具体是什么,有哪些不同?今天查了些资料,来解解惑 ...

  2. 关于cpu体系架构的一些有趣的故事分享

    从排查一次匪夷所思的coredump,引出各种体系架构的差异. 本文中的所有内容来自学习DCC888的学习笔记或者自己理解的整理,如需转载请注明出处.周荣华@燧原科技 1 背景 从全世界有记载的第一台 ...

  3. 说说面向服务的体系架构SOA

    序言 在.Net的世界中,一提及SOA,大家想到的应该是Web Service,WCF,还有人或许也会在.NET MVC中的Web API上做上标记,然后泛泛其谈! 的确,微软的这些技术也确实推动着面 ...

  4. 面向服务的体系架构SOA

    面向服务的体系架构SOA 序言 在.Net的世界中,一提及SOA,大家想到的应该是Web Service,WCF,还有人或许也会在.NET MVC中的Web API上做上标记,然后泛泛其谈! 的确,微 ...

  5. 代码 or 指令,浅析ARM架构下的函数的调用过程

    摘要:linux程序运行的状态以及如何推导调用栈. 1.背景知识 1.ARM64寄存器介绍: 2.STP指令详解(ARMV8手册): 我们先看一下指令格式(64bit),以及指令对于寄存机执行结果的影 ...

  6. ARM架构下的Docker环境,OpenJDK官方没有8版本镜像,如何完美解决?

    为什么需要ARM架构下的OpenJDK8的Docker镜像? 对现有的Java应用,之前一直运行在x86处理器环境下,编译和运行都是JDK8,如今在树莓派的Docker环境运行(或者其他ARM架构电脑 ...

  7. InnoDB体系架构(四)Master Thread工作方式

    Master Thread工作方式 在前面的文章:InnoDB体系架构——后台线程 说到:InnoDB存储引擎的主要工作都是在一个单独的后台线程Master Thread中完成.这篇具体介绍该线程的具 ...

  8. ARM系统架构

    ARM系统架构 一.ARM概要 ARM架构,曾称进阶精简指令集机器(Advanced RISC Machine)更早称作Acorn RISC Machine,是一个32位精简指令集(RISC)处理器架 ...

  9. CI Weekly #5 | 微服务架构下的持续部署与交付

    CI Weekly 围绕『 软件工程效率提升』 进行一系列技术内容分享,包括国内外持续集成.持续交付,持续部署.自动化测试. DevOps 等实践教程.工具与资源,以及一些工程师文化相关的程序员 Ti ...

随机推荐

  1. textarea高度自适应

    var tx=document.getElementById("tx"); tx.style.height=tx.scrollHeight+"px" tx.st ...

  2. 基于CentOS与VmwareStation10搭建Oracle11G RAC 64集群环境:4.安装Oracle RAC FAQ-4.5.安装Grid,创建ASM磁盘组空间不足

    因之前分区时,分区的Last cylinder的值选了“1”,导致创建磁盘组空间不足.解决办法是先删除分区,重新创建分区并删除ASM磁盘,然后重建ASM磁盘 1. 先删除分区,重新创建分区: 1)查询 ...

  3. JS URL编码

    JS URL编码escape() 方法: 采用ISO Latin字符集对指定的字符串进行编码.所有的空格符.标点符号.特殊字符以及其他非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符在 ...

  4. selenium python (二) 元素定位方法

    定位的原则就是:id name class 优先,强烈建议和前端哥哥搞好关系.定位就不是问题:实在不行就xpath和css大部分偶可以定位到. webdriver定位的方法是通过一个By类,By类中有 ...

  5. Java中的路径问题

    Java中的路径问题 代码说明,如下: package com.merlin.test; import java.io.InputStream; public class Test { public ...

  6. Epic - Tic Tac Toe

    N*N matrix is given with input red or black.You can move horizontally, vertically or diagonally. If ...

  7. android LocalActivityManager说明

          类概述 Helper class for managing multiple running embedded activities in the same process. This c ...

  8. activity切换时的overridePendingTransition动画效

      注意,切换方法overridePendingTransition只能在startActivity和finish方法之后调用.第一个参数为第一个Activity离开时的动画,第二参数为所进入的Act ...

  9. ubuntu下Qt cannot find -lGL错误的解决方法 (转载)

    在ubuntu下使用Qt 编译时候遇上了cannot find -lGL错误,使用命令 sudo apt-get install libqt4-dev或者sudo apt-get install li ...

  10. android开发中遇到的bug

    这种NullPointerException这么解决啊 Activity.dispatchTouchEvent 里try catch一下 参考:http://www.eoeandroid.com/th ...