原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

多核系统中,单个的机器指令就不是原子操作,因为多核系统里是多指令流并行运行的,一个核在执行一个指令时,其他核同时执行的指令有可能操作同一块内存区域,从而出现数据竞争现象。多核系统中的原子操作通常使用内存栅障(memory barrier)来实现,即一个CPU核在执行原子操作时,其他CPU核必须停止对内存操作或者不对指定的内存进行操作,这样才能避免数据竞争问题。

在开发中,我们的经常会遇到这样的程序逻辑操作:

1.读一个位于内存中的全局变量(值读取到寄存器中)。

2.修改该变量的值(也就是修改寄存器中的值)。

3.将修改的值回写到全部变量(将寄存器中的数值写回内存中的变量值)。

如果这个操作序列是串行化的操作(在一个thread中串行执行),那么一切没问题,但在线程与中断或多个CPU内核控制路径同时并行执行上面操作序列,有可能发生下面的场景:

线程调用的控制路径

中断handler控制路径

读操作

读操作

修改

写操作

修改

写操作

线程调用的控制路径上,完成读操作后,硬件触发中断,开始执行中断handler。这种场景下,中断handler控制路径的写回的操作被系统调用控制路径上的写回覆盖了,结果也是错误的。

CPU1上的操作

CPU2上的操作

读操作

读操作

修改

修改

写操作

写操作

多个CPUs和memory chip是通过总线互联的,在任意时刻,只能有一个总线设备访问该memory chip。来自两个CPU上的读memory操作被串行化执行,分别获得了同样的旧值。完成修改后,两个CPU都想进行写操作,把修改的值写回到memory。但是硬件限制使得CPU的写回必须是串行化的,因此CPU1首先获得了访问权,进行写回动作,随后,CPU2完成写回动作。在这种情况下,CPU1的对memory的修改被CPU2的操作覆盖了,因此执行结果是错误的。

对于那些有多个控制路径进行read-modify-write的变量,我们使用ldrex和strex这两条汇编指令来实现原子操作,ldr和str这两条指令大家应该熟悉,后缀的ex表示Exclusive,是ARMv7提供的为了实现同步的汇编指令。下面看看这两条指令的使用方式。

LDREX  <Rt>, [<Rn>]

<Rn>是base register,保存memory的address,LDREX指令从base register中获取memory address,并且将memory的内容加载到<Rt>(destination register)中。这些操作和ldr的操作是一样的,那么如何体现exclusive呢?其实,在执行这条指令的时候,会有监视器local monitor观察状态

STREX <Rd>, <Rt>, [<Rn>]

STREX跟LDREX指令类似,<Rn>是base register,保存memory的address,STREX指令从base register中获取memory address,并且将<Rt> (source register)中的内容加载到该memory中。这里的<Rd>保存了memeory 更新成功或者失败的结果,0表示memory更新成功,1表示失败。STREX指令是否能成功执行是和local monitor状态相关的。下面的表格可以描述这种情况。

thread 1

thread 2

local monitor的状态

Open Access state

LDREX

Exclusive Access state

LDREX

Exclusive Access state

Modify

Exclusive Access state

STREX

Open Access state

Modify

Open Access state

STREX

在Open Access state的状态下,执行STREX指令会导致该指令执行失败

保持Open Access state,直到下一个LDREX指令

开始的时候,local monitor处于Open Access state的状态,thread 1执行LDREX 命令后,local monitor的状态迁移到Exclusive Access state(标记本地CPU对xxx地址进行了LDREX的操作),这时候,中断发生了,在中断handler中,又一次执行了LDREX ,这时候,local monitor的状态保持不变,直到STREX指令成功执行,local monitor的状态迁移到Open Access state的状态(清除xxx地址上的LDREX的标记)。返回thread 1的时候,在Open Access state的状态下,执行STREX指令会导致该指令执行失败(没有LDREX的标记,何来STREX),说明有其他的内核控制路径做了对内存的操作。

大概的原理已经描述完毕,下面回到具体实现。我们定义一个特殊的类型atomic_t,具体定义如下:

struct os_atomic
{volatile os_int32_t counter;
};
typedef struct os_atomic os_atomic_t;

os_atomic_t实际上就是一个os_int32_t 类型的counter,不过定义这样特殊的类型os_atomic_t是有其思考的:内核定义了若干接口API函数,这些函数只会接收os_atomic_t类型的参数。可以确保os_atomic_xxx的接口函数只会操作os_atomic_t类型的数据。同样的,如果你定义了os_atomic_t类型的变量(你期望用os_atomic_xxx的接口API函数操作它),这些变量也不会被那些普通的、非原子变量操作的API函数接受,下面看看原子操作函数的一些实现:

/* 给一个原子变量mem增加value */
__asm void os_atomic_add(os_atomic_t *mem, os_int32_t value)
{
Loop_addLDREX R2, [R0]ADD   R2, R2, R1STREX R3, R2, [R0]CBZ   R3, Loop_add_exitB     Loop_add
Loop_add_exit    BX    LR
}
/* 给一个原子变量mem增加value,将最新的mem值返回 */
__asm os_int32_t os_atomic_add_return(os_atomic_t *mem, os_int32_t value)
{
Loop_add_retLDREX R2, [R0]ADD   R2, R2, R1STREX R3, R2, [R0]CBZ   R3, Loop_add_ret_exitB     Loop_add_ret
Loop_add_ret_exitMOV   R0, R2BX    LR
}
/* 给一个原子变量mem减去value */
__asm void os_atomic_sub(os_atomic_t *mem, os_int32_t value)
{
Loop_subLDREX R2, [R0]SUB   R2, R2, R1STREX R3, R2, [R0]CBZ   R3, Loop_sub_exitB     Loop_sub
Loop_sub_exitBX    LR
}
/* 给一个原子变量mem减去value,将最新减去的值返回 */
__asm os_int32_t os_atomic_sub_return(os_atomic_t *mem, os_int32_t value)
{
Loop_sub_retLDREX R2, [R0]SUB   R2, R2, R1STREX R3, R2, [R0]CBZ   R3, Loop_sub_ret_exitB     Loop_sub_ret
Loop_sub_ret_exitMOV   R0, R2BX    LR
}
/* 原子变量mem的值加一 */
__asm void os_atomic_inc(os_atomic_t *mem)
{
Loop_incLDREX R2, [R0]ADD   R2, R2, #1STREX R3, R2, [R0]CBZ   R3, Loop_inc_exitB     Loop_inc
Loop_inc_exitBX    LR
}
/* 原子变量mem的值加一并将最新值返回 */
__asm os_int32_t os_atomic_inc_return(os_atomic_t *mem)
{
Loop_inc_retLDREX R2, [R0]ADD   R2, R2, #1STREX R3, R2, [R0]CBZ   R3, Loop_inc_ret_exitB     Loop_inc_ret
Loop_inc_ret_exitMOV   R0, R2BX    LR
}
/* 原子变量mem的值减一 */
__asm void os_atomic_dec(os_atomic_t *mem)
{
Loop_decLDREX R2, [R0]SUB   R2, R2, #1STREX R3, R2, [R0]CBZ   R3, Loop_dec_exitB     Loop_dec
Loop_dec_exitBX    LR
}
/* 原子变量mem的值减一并将最新的值返回 */
__asm os_int32_t os_atomic_dec_return(os_atomic_t *mem)
{
Loop_dec_retLDREX R2, [R0]SUB   R2, R2, #1STREX R3, R2, [R0]CBZ   R3, Loop_dec_ret_exitB     Loop_dec_ret
Loop_dec_ret_exitMOV   R0, R2BX    LR
}
/* 将value的值写入到原子变量mem中 */
__asm os_int32_t os_atomic_xchg(os_atomic_t* mem, os_int32_t value)
{
Loop_xchgLDREX R2, [R0]    STREX R3, R1, [R0]CBZ   R3, Loop_xchg_exitB     Loop_xchg
Loop_xchg_exitMOV   R0, R2BX    LR
}
/* 比较old和原子变量mem中的值,如果相等,那么就把new值赋给原子变量。返回旧的原子变量mem中的值 */
__asm os_bool_t os_atomic_cmpxchg(os_atomic_t* mem, os_int32_t old, os_int32_t new)
{PUSH  {R4}
Loop_cmpxchgLDREX R3, [R0]MOV   R4, #0TEQ   R3, R1BNE   Loop_cmpxchg_exitSTREX R3, R2, [R0]MOV   R4, #1CBZ   R3, Loop_cmpxchg_exitB     Loop_cmpxchg
Loop_cmpxchg_exitMOV   R0, R4POP   {R4}BX    LR
}
/* 原子变量mem与value进行按位与操作 */
__asm void os_atomic_and(os_atomic_t* mem, os_int32_t value)
{
Loop_andLDREX R2, [R0]AND   R2, R1STREX R3, R2, [R0]CBZ   R3, Loop_and_exitB     Loop_and
Loop_and_exitBX    LR
}
/* 原子变量mem与value进行按位或操作 */
__asm void os_atomic_or(os_atomic_t* mem, os_int32_t value)
{
Loop_orLDREX R2, [R0]ORR   R2, R1STREX R3, R2, [R0]CBZ   R3, Loop_or_exitB     Loop_or
Loop_or_exitBX    LR
}
/* 原子变量mem与value进行异或操作 */
__asm void os_atomic_xor(os_atomic_t* mem, os_int32_t value)
{
Loop_xorLDREX R2, [R0]EOR   R2, R1STREX R3, R2, [R0]CBZ   R3, Loop_xor_exitB     Loop_xor
Loop_xor_exitBX    LR
}
/* 判断原子变量mem的某一位是否等于1 */
__asm os_bool_t os_atomic_test_bit(os_atomic_t* mem, os_int32_t nr)
{PUSH  {R4, R5}MOV   R4, #1LSL   R4, R1
Loop_testLDREX R2, [R0]AND   R5, R2, R4LSR   R5, R1STREX R3, R2, [R0]CBZ   R3, Loop_test_exitB     Loop_test
Loop_test_exitMOV   R0, R5POP   {R4, R5}BX    LR
}
/* 原子变量mem的值某一位置1 */
__asm void os_atomic_set_bit(os_atomic_t* mem, os_int32_t nr)
{PUSH  {R4}MOV   R4, #1LSL   R4, R1
Loop_setLDREX R2, [R0]ORR   R2, R4STREX R3, R2, [R0]CBZ   R3, Loop_set_exitB     Loop_set
Loop_set_exitPOP   {R4}BX    LR
}
/* 原子变量mem的值某一位清零 */
__asm void os_atomic_clear_bit(os_atomic_t* mem, os_int32_t nr)
{PUSH  {R4}MOV   R4, #1LSL   R4, R1
Loop_clearLDREX R2, [R0]BIC   R2, R4STREX R3, R2, [R0]CBZ   R3, Loop_clear_exitB     Loop_clear
Loop_clear_exitPOP   {R4}BX    LR
}
__asm void os_atomic_change_bit(os_atomic_t* mem, os_int32_t nr)
{PUSH  {R4}MOV   R4, #1LSL   R4, R1
Loop_changeLDREX R2, [R0]EOR   R2, R4STREX R3, R2, [R0]CBZ   R3, Loop_change_exitB     Loop_change
Loop_change_exitPOP   {R4}BX    LR
}

armv7实现原子操作相关推荐

  1. C++11 原子类型与原子操作

    文章目录 1.认识原子操作 2.C++11 实现原子操作 3.内存模型:强顺序与弱顺序 参考文献 1.认识原子操作 原子操作是在多线程程序中"最小的且不可并行化的"操作,意味着多个 ...

  2. 原子操作 - ARM汇编同步机制实例(一)

    为了实现线程间同步,一般都要在执行关键代码段之前加互斥(Mutex)锁,且在执行完关键代码段之后解锁.为了实现所谓的互斥锁的概念,一般都需要所在平台提供支持.本文主要用来说明ARM平台上特有的独占访问 ...

  3. Linux内核同步机制之(一):原子操作

    作者: 郭健 来源: wowotech 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个位于memory中的变量的值到寄存器中 2.修改该变量的值(也就是修改寄存器中的值) 3.将寄存器中 ...

  4. Java并发编程(十一)——原子操作CAS

    一.原子操作 syn基于阻塞的锁的机制,1.被阻塞的线程优先级很高,2.拿到锁的线程一直不释放锁怎么办?3.大量的竞争,消耗cpu,同时带来死锁或者其他安全. CAS的原理 CAS(Compare A ...

  5. 关于 线程模型中经常使用的 __sync_fetch_and_add 原子操作的性能

    最近从 kvell 这篇论文中看到一些单机存储引擎的优秀设计,底层存储硬件性能在不远的未来可能不再是主要的性能瓶颈,反而高并发下的CPU可能是软件性能的主要限制.像BPS/AEP/Optane-SSD ...

  6. 【C++】多线程与原子操作和无锁编程【五】

    [C++]多线程与原子操作和无锁编程[五] 1.何为原子操作 前面介绍了多线程间是通过互斥锁与条件变量来保证共享数据的同步的,互斥锁主要是针对过程加锁来实现对共享资源的排他性访问.很多时候,对共享资源 ...

  7. windows线程同步-原子操作-Interlocked系列函数(用户模式)

    Interlocked系列函数用来保证原子访问. InterlockedExchangeAdd提供保证long类型的原子操作. InterlockedExchangeAdd64提供long long ...

  8. CAS、原子操作类的应用与浅析及Java8对其的优化

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:CoderBear juejin.im/post/5c7a8 ...

  9. Java原子操作类,你知道多少?

    原子操作类简介 由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案. 实际上,在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去 更新基 ...

最新文章

  1. 从IoT World 2019看全球IoT九大发展趋势
  2. ASP.NET 2.0的异步页面刷新真给劲
  3. idea实现抽象类的所有抽象方法_深入理解Java的接口和抽象类
  4. mysqldump命令
  5. 207-Course Schedule
  6. mac下npm/node的安装和卸载、升级;node、npm升级后最后删掉node_modules重新安装
  7. 高级cmd攻击命令_一步一步学习DVWA渗透测试(Command Injection命令行注入)-第七次课...
  8. python软件菜单如何设计_佩服!我用Python设计了一个签名软件
  9. 基础知识(十二)Opengl回顾记录
  10. Linux的一些简单命令操作
  11. RTT时钟管理篇——RTT定时器超时判断理解
  12. 在Extjs中对日期的处理,以及在后端数据在SQL语句的判断处理
  13. vue2强制刷新,解决页面不会重新渲染的问题
  14. 打印从1到k之间的所有素数
  15. C# 根据空格数截取
  16. UE4之A点绕B点旋转
  17. 【MMD动作+镜头】Bo Peep Bo Peep
  18. 国家对五险一金的交纳说明(已更新)
  19. /deep/深度作用域选择器
  20. java dao 是什么_dao java是什么

热门文章

  1. 温岭创新计算机网络技术有限公司,创新永无止境,服务从心开始
  2. 计算机网络access,计算机网络二级access怎么考
  3. 微信小程序 slider双向滑动渐变色
  4. pcie1 4 速度_守门员丨门将的各种速度训练方法
  5. 数字电子技术基础 学习笔记1
  6. 云顶之弈两个传送门_云顶之弈兹若特传送门合成公式 云顶之弈兹若特传送门怎么合成...
  7. 手机技巧:常见的Note、Max、Pro、Plus、Mate系列介绍,看完你就懂了!
  8. 前端在线播放flv视频
  9. JavaWeb《二》Servlet、Request请求
  10. 史上最全的vue.js源码解析(四)