深入浅出ARM原子操作

概述

很多情况下我们只是需要一个简单的、高效的、线程安全的递增递减方案。

  • 简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易;
  • 高效意味着耗用资源要少,程序处理速度要快;
  • 线程安全也非常重要,这个在多线程下能保证数据的正确性。

这三个条件看起来比较简单,但是实现起来却难以令人满意。原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为"不可被中断的一个或一系列操作" 。此外,在多处理器上实现原子操作就变得更复杂。

相关术语定义如下:

gcc内置的原子操作

gcc从4.1.2提供了__sync_*系列的built-in函数,用于提供加减和逻辑运算的原子操作。其声明如下:

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, ...)

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, ...)

type可以是1,2,4或8字节长度的int类型,即:
int8_t / uint8_t
int16_t / uint16_t
int32_t / uint32_t
int64_t / uint64_t
后面的可扩展参数(...)用来指出哪些变量需要memory barrier,因为目前gcc实现的是full barrier(类似于linux kernel 中的mb(),表示这个操作之前的所有内存操作不会被重排序到这个操作之后),所以可以略掉这个参数。

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写入*ptr,
第一个函数在相等并写入的情况下返回true.
第二个函数在返回操作之前的值。

__sync_bool_compare_and_swap的实现是采用了dmb,ldrex和strex指令。

测试的C语言代码如下:

		int sum = 0;

		int old = sum;
		for (int i = 0; i < 1000000; i++)  // 百万次
		{
			while (!__sync_bool_compare_and_swap(&sum, old, old + 1))  // 如果old等于sum, 就把old+1写入sum
			{
				old = sum; // 更新old
			}
		}

汇编伪代码实现如下:

 do
  {
    while ( 1 )
    {
      __dmb();
      do
      {
        v2 = __ldrex((unsigned __int32 *)&sum);
        v3 = v2 == v1;
        if ( v2 != v1 )
          break;
        v4 = __strex(v1 + 1, (unsigned int *)&sum);
        v3 = v4 == 0;
      }
      while ( v4 );
      __dmb();
      if ( v3 )
        break;
      v1 = sum;
    }
    --v0;
  }
  while ( v0 );

 

C++11的原理操作

atomic对int、char、bool等数据结构进行了原子性封装,在多线程环境中,对std::atomic对象的访问不会造成竞争-冒险。利用std::atomic可实现数据结构的无锁设计。

通过查看汇编,std::atomic的底层实现也是采用dmb,ldrex和strex指令实现的。

在以往的C++标准中并没有对原子操作进行规定,我们往往是使用汇编语言,或者是借助第三方的线程库,例如intel的pthread来实现。在新标准C++11,引入了原子操作的概念,并通过这个新的头文件提供了多种原子操作数据类型,例如,atomic_bool,atomic_int等等,如果我们在多个线程中对这些类型的共享资源进行操作,编译器将保证这些操作都是原子性的,也就是说,确保任意时刻只有一个线程对这个资源进行访问,编译器将保证,多个线程访问这个共享资源的正确性。从而避免了锁的使用,提高了效率。

memory barrier

cpu会对我们的指令进行排序,一般说来会提高程序的效率,但有时候可能造成我们不希望得到的结果,举一个例子,比如我们有一个硬件设备,它有4个寄存器,当你发出一个操作指令的时候,一个寄存器存的是你的操作指令(比如READ),两个寄存器存的是参数(比如是地址和size),最后一个寄存器是控制寄存器,在所有的参数都设置好之后向其发出指令,设备开始读取参数,执行命令,程序可能如下:

    write1(dev.register_size,size);
    write1(dev.register_addr,addr);
    write1(dev.register_cmd,READ);
    write1(dev.register_control,GO);


如果最后一条write1被换到了前几条语句之前,那么肯定不是我们所期望的,这时候我们可以在最后一条语句之前加入一个memory barrier,强制cpu执行完前面的写入以后再执行最后一条:

    write1(dev.register_size,size);
    write1(dev.register_addr,addr);
    write1(dev.register_cmd,READ);
    __sync_synchronize();
    write1(dev.register_control,GO);


memory barrier有几种类型:
    acquire barrier : 不允许将barrier之后的内存读取指令移到barrier之前(linux kernel中的wmb())。
    release barrier : 不允许将barrier之前的内存读取指令移到barrier之后 (linux kernel中的rmb())。
    full barrier    : 以上两种barrier的合集(linux kernel中的mb())。

ldrex和strex指令

LDREX和STREX指令,是将单纯的更新内存的原子操作分成了两个独立的步骤。

LDREX Rx, [Ry]

LDREX用来读取内存中的值,并标记对该段内存的独占访问。上面的指令意味着,读取寄存器Ry指向的4字节内存值,将其保存到Rx寄存器中,同时标记对Ry指向内存区域的独占访问。如果执行LDREX指令的时候发现已经被标记为独占访问了,并不会对指令的执行产生影响。


STREX Rx, Ry, [Rz]

STREX在更新内存数值时,会检查该段内存是否已经被标记为独占访问,并以此来决定是否更新内存中的值。如果执行这条指令的时候发现已经被标记为独占访问了,则将寄存器Ry中的值更新到寄存器Rz指向的内存,并将寄存器Rx设置成0。指令执行成功后,会将独占访问标记位清除。而如果执行这条指令的时候发现没有设置独占标记,则不会更新内存,且将寄存器Rx的值设置成1。一旦某条STREX指令执行成功后,以后再对同一段内存尝试使用STREX指令更新的时候,会发现独占标记已经被清空了,就不能再更新了,从而实现独占访问的机制。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ARM7 LPC2300是一款基于ARM7核心的微控制器,LPC2300系列具有较高的性能和丰富的外设资源,适用于广泛的嵌入式应用。深入浅出ARM7 LPC2300 PDF是一本介绍该微控制器的电子书或文档,旨在以简明易懂的方式解释和展示LPC2300的特点和功能。 这本PDF可能会从以下几个方面进行介绍。 首先,它会详细介绍LPC2300系列微控制器的基本架构和核心特性。ARM7核心具有良好的代码密度和高效的指令集,使得LPC2300能够高效地执行各种任务。此外,LPC2300还具有丰富的存储器接口,包括闪存、SRAM和外部总线接口,以支持不同的存储器要求。 其次,它会介绍LPC2300系列微控制器的外设资源。LPC2300内置了多个通用串行接口、定时器、PWM输出、模数转换器等功能模块,可提供各种输入输出接口和信号处理功能。通过对这些外设资源的深入理解,开发人员可以充分利用LPC2300的功能来满足特定应用的需求。 此外,PDF还可能介绍如何在开发过程中使用工具链和开发环境来编写、编译和调试LPC2300的程序。这些工具和环境可以简化开发流程,提高开发效率。 最后,PDF还可能提供示例代码和应用案例,帮助开发人员更好地理解和应用LPC2300。示例代码可以展示如何使用不同的外设资源和功能模块来实现特定的任务,而应用案例则可以使开发人员在实际项目中应用所学知识。 总的来说,深入浅出ARM7 LPC2300 PDF将以简明易懂的方式介绍和解释LPC2300系列微控制器的特点和功能,帮助开发人员更好地理解和应用该微控制器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值