处理器在拜访同享资源时,有必要对临界区进行同步,即确保同一时间内,只要一个对临界区的拜访者。
当同享资源为一内存地址时,原子操作是对该类型同享资源同步拜访的最佳方法。
跟着运用的日益杂乱和SMP的广泛运用,处理器都开端供给硬件同步原语以支撑原子地更新内存地址。
CISC处理器比方IA32,能够供给独自的多种原子指令完结杂乱的原子操作,由处理器确保读-修正-写回进程的原子性。
而RISC则不同,因为除Load和Store的一切操作都有必要在寄存器中完结,
怎么确保从装载内存地址到寄存器,到修正寄存器中的值,再到将寄存器中的值写回内存中能够原子性的完结,便成为了处理器规划的要害。
从ARMv6架构开端,ARM处理器供给了Exclusive accesses同步原语,包括两条指令:
LDREXSTREX
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 的功用。
1 static inline void atomic_add(int i, atomic_t *v)2 {3 unsigned long tmp;4 int result;5 6 __asm__ __volatile__("@ atomic_add\n"7 "1: ldrex %0, [%3]\n"8 " add %0, %0, %4\n"9 " strex %1, %0, [%3]\n"10 " teq %1, #0\n"11 " bne 1b"12 : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)13 : "r" (&v->counter), "Ir" (i)14 : "cc");15 }
在第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 不行用于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指令的履行成果将不行预知。
体系结构
ARMLDREX和STREX可用于 ARMv6 及更高版别中。
ARMLDREXB、LDREXH、LDREXD、STREXB、STREXD和STREXH可用于 ARMv6K 及更高版别中。
一切这些 32 位 Thumb 指令均可用于 ARMv6T2 及更高版别,但LDREXD和STREXD在 ARMv7-M 架构中不行用。
这些指令均无 16 位版别。
示例
MOV r1, #0x1 ; load the ‘lock taken’ valuetryLDREX 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
arm/include/asm/atomic.h?v=2.6.33″ rel=”external nofollow noreferrer” target=”_blank”>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#include #include #define ATOMIC_INIT(i) { (i) }#ifdef __KERNEL__/** On ARM, ordinary assignment (str instruction) doesnt 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#endifstatic 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 #endif#endif