全局变量中断原子操作_原子操作原理分析
原子操作原理分析
概念
原子操作是指不被打断的操作,即它是最小的执行单位。最简单的原子操作就是一条条的汇编指令(不包括一些伪指令,伪指令会被汇编器解释成多条汇编指令)。在 linux 中原子操作对应的数据结构为 atomic_t,定义如下:
typedef struct {int counter;
} atomic_t;
本质上就是一个整型变量,之所以定义这么一个数据类型,是为了让原子操作函数只接受 atomic_t 类型的操作数,如果传入的不是 atomic_t 类型数据,在程序编译阶段就不会通过;另一个原因就是确保编译器不会对相应的值进行访问优化,确保对它的访问都是对内存的访问,而不是对寄存器的访问。
赋值操作
ARM 处理器有直接对内存地址进行赋值的指令(STR)。
#define atomic_set(v,i) (((v)->counter) = (i))
读操作
用 volatile 来防止编译器对变量访问的优化,确保是对内存的访问,而不是对寄存器的访问。
#define atomic_read(v) (*(volatile int *)&(v)->counter)
加操作
使用独占指令完成累加操作。
static inline void atomic_add(int i, atomic_t *v){unsigned long tmp;int result;// 使用独占指令读取,然后执行加操作,独占写失败时就重新执行 __asm__ __volatile__("@ atomic_addn""1: ldrex %0, [%3]n"" add %0, %0, %4n"" strex %1, %0, [%3]n"" teq %1, #0n"" bne 1b": "=&r" (result), "=&r" (tmp), "+Qo" (v->counter): "r" (&v->counter), "Ir" (i): "cc");}
减操作
对比加操作和减操作的代码可以看出,它们非常的相似,其实不同的地方就一句,所以现在最新的内核源码中已经使用宏定义 ATOMIC_OP(op, c_op, asm_op) 来重写了这部分代码。
static inline void atomic_sub(int i, atomic_t *v){unsigned long tmp;int result;// 使用独占指令读取,然后执行减操作,独占写失败时就重新执行 __asm__ __volatile__("@ atomic_subn""1: ldrex %0, [%3]n"" sub %0, %0, %4n"" strex %1, %0, [%3]n"" teq %1, #0n"" bne 1b": "=&r" (result), "=&r" (tmp), "+Qo" (v->counter): "r" (&v->counter), "Ir" (i): "cc");}
其他操作
类似的原子操作函数还有一些,比如 atomic_XXX_return、atomic_cmpxchg、atomic_clear_mask,以及在此基础上实现的 atomic_inc、atomic_dec、atomic_XXX_and_test、atomic_XXX_return等。以上代码都是针对 SMP 处理器的实现方式,针对非 SMP 处理器,由于不存在其他核心的抢占,所以只需要防止其他进程抢占即可实现原子操作,例如加操作:
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;
}
ldrex 和 strex 使用说明
在Armv6开始支持多核,通过ldrex与strex指令来保证数据操作的原子性,比如自旋锁的上锁操作、原子变量操作等。在Armv6之前,都是单核,为保证数据的原子性,需要进行关中断操作。对于多核平台,关中断操作只能关闭本核中断,要想对数据进行原子操作,必须借助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 不可用于 Rd、Rt、Rt2 或 Rn 中的任何一个。 对于 STREX,Rd 一定不能与Rt、Rt2 或Rn 为同一寄存器。 对于 ARM 指令:
Rt 必须是一个编号为偶数的寄存器,且不能为 r14
Rt2 必须为R(t+1)
不允许使用 offset。
对于 Thumb 指令:
r13 不可用于 Rd、Rt 或Rt2 中的任何一个
对于 LDREXD,Rt 和Rt2 不可为同一个寄存器
offset 的值可为 0-1020 范围内 4 的任何倍数。
用法
利用 LDREX 和 STREX 可在多个处理器和共享内存系统之前实现进程间通信。
出于性能方面的考虑,请将相应 LDREX 指令和 STREX 指令间的指令数控制到最少。
Note
STREX 指令中所用的地址必须要与近期执行次数最多的 LDREX 指令所用的地址相同。如果使用不同的地址,则STREX 指令的执行结果将不可预知。
示例
MOV r1, #0x1 ; load the ‘lock taken’ value
tryLDREX r0, [LockAddr] ; load the lock valueCMP r0, #0 ; is the lock free?STREXEQ r0, r1, [LockAddr] ; try and claim the lockCMPEQ r0, #0 ; did this succeed?BNE try ; no – try again.... ;yes – we have the lock
ldrex使用问题
ldrex与strex指令的结合确实强大,对于一些古老的多核处理器,需要锁总线来保证数据原子操作(如x86),这样会导致访问效率降低。而ldrex和strex没有进行锁总线操作,并且在两条指令之间可以对变量进行复杂的操作,不仅仅是加1减1操作。但是也需要注意ldrex与strex指令要想正常工作,也是有前提的,笔者在开发ti 66AH2H(4核cortex-A15)平台BSP时,就遇到了这个问题,先看下ARM手册对于ldrex指令的相关说明:
上述内容来源于ARM官方文档(DDI0438I_cortex_a15_r4p0_trm.pdf),主要表达意思就是对于ldrex和strex指令的支持需要global monitor,而global monitor的实现由两种,一种是内部实现,需要开启cache。另一种是外部实现(和芯片有关系,有的芯片没有实现),通过总线监听的方式。当内部和外部都实现时,优先使用内部global monitor。为安全起见,尽量不在开启cache前使用ldrex和strex指令。
全局变量中断原子操作_原子操作原理分析相关推荐
- 全局变量中断原子操作_操作系统导论02-06章
第二章 操作系统介绍 一个正在运行的程序会做一件非常简单的事情:执行指令.处理器从内存中获取(fetch)一条指令,对其进行解码(decode)(弄清楚这是哪条指令),然后执行(execute)它(做 ...
- 全局变量中断原子操作_中断函数里改变一个全局变量的值,在主函数里却检测到未变化...
如题.下面是我的程序代码 /****************************************************************** 键盘扫描函数 使用CPU资源:PORT ...
- c++ 原子操作 赋值_原子操作原理
1. 概念 原子操作是指不被打断的操作,即它是最小的执行单位.最简单的原子操作就是一条条的汇编指令(不包括一些伪指令,伪指令会被汇编器解释成多条汇编指令).在 linux 中原子操作对应的数据结构为 ...
- rk3288 原子操作和原子位操作
一.原子变量内核操作函数 Linux中有2中原子操作: 原子变量.原子位. 原子变量的内核操作函数 原子变量的操作函数在arch/arm/include/asm/atomic.h中 原子变量类型如下, ...
- 高通Android智能平台环境搭建_编译流程分析
高通Android智能平台环境搭建_编译流程分析 高通平台环境搭建,编译,系统引导流程分析 TOC \o \h \z \u 1. 高通平台android开发总结. 7 1.1 搭建高通平台环境开发环境 ...
- 进程上下文、中断上下文及原子上下文
谈论进程上下文 .中断上下文 . 原子上下文之前,有必要讨论下两个概念: a -- 上下文 上下文是从英文context翻译过来,指的是一种环境.相对于进程而言,就是进程执行时的环境: 具体来说就是各 ...
- 《Linux操作系统 - 驱动开发》第9章 进程上下文、中断上下文及原子上下文
谈论进程上下文 .中断上下文.原子上下文之前,有必要讨论下两个概念: a – 上下文 上下文是从英文context翻译过来,指的是一种环境.相对于进程而言,就是进程执行时的环境: 具体来说就是各个变量 ...
- 消耗性缺口_衰竭缺口分析
消耗性缺口:股价在大幅度波动过程中价格在奄奄一息中回光反照,作最后一次跳跃,然而,最后的挣扎好景不长,在随后的几天乃至一个星期里的价格马上开始下滑.当收市价格低于这种最后的跳空后,表明衰竭跳空已经形成 ...
- 5.2 C++中的原子操作和原子类型
5.2 C++中的原子操作和原子类型 原子操作是一类不可分割的操作,当这样操作在任意线程中进行一半的时候,你是不能查看的:它的状态要么是完成,要不就是未完成.如果从对象中读取一个值的操作是原子的,并且 ...
最新文章
- String、StringBuilder、StringBuffer的比较
- linux 创建虚拟IP
- 针对SSL/TLS的拒绝服务攻击以及使用ettercap进行DNS欺骗
- 全志 增加强制横屏标志 Patch
- xtrabackup支持的engine
- curl liinux下http命令执行工具
- 洗礼灵魂,修炼python(8)--高效的字典
- chrome 插件下载
- 在Oracle中使用Guid
- Java性能优化的五种方式,让你的Java程序更快、更稳定!
- python儿童编程入门-如何让孩子轻松学习Python编程
- Python量化数据获取:总资产同比增长率与净资产同比增长率
- 【游戏开发进阶】玩转贝塞尔曲线,教你在Unity中画Bezier贝塞尔曲线(二阶、三阶),手把手教你推导公式
- 我和计算机专业的故事
- Chrome隐私设置错误,您的链接不是私密连接
- Android App links 链接打开app功能
- Touch screen
- 传奇世界私服务器端制作,关于内网架设传奇世界私服问题的一些解答
- ARFoundation从零开始3-创建ARFoundation项目
- 好毒的电商导流上网站