一. 原子数据类型:
typedef struct {
int counter;
}atomic_t;
二. 基本的原子操作函数接口:
接口函数 | 描述 |
static inline void atomic_add(int i, atomic_t *v) | 给一个原子变量v增加i |
static inline int atomic_add_return(int i, atomic_t *v) | 同上,只不过将变量v的最新值返回 |
static inline void atomic_sub(int i, atomic_t *v) | 给一个原子变量v减去i |
static inline int atomic_sub_return(int i, atomic_t *v) | 同上,只不过将变量v的最新值返回 |
static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new) | 比较old和原子变量ptr中的值,如果相等,那么就把new值赋给原子变量。 返回旧的原子变量ptr中的值 |
atomic_read | 获取原子变量的值 |
atomic_set | 设定原子变量的值 |
atomic_inc(v) | 原子变量的值加一 |
atomic_inc_return(v) | 同上,只不过将变量v的最新值返回 |
atomic_dec(v) | 原子变量的值减去一 |
atomic_dec_return(v) | 同上,只不过将变量v的最新值返回 |
atomic_sub_and_test(i, v) | 给一个原子变量v减去i,并判断变量v的最新值是否等于0 |
atomic_add_negative(i,v) | 给一个原子变量v增加i,并判断变量v的最新值是否是负数 |
static inline int atomic_add_unless(atomic_t *v, int a, int u) | 只要原子变量v不等于u,那么就执行原子变量v加a的操作。 如果v不等于u,返回非0值,否则返回0值 |
三. 以atomic_add_return函数实现为例:
代码在Linux内核中的路径: /arch/arm/include/asm/atomic.h
#if __LINUX_ARM_ARCH__ >= 6
.....
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, [%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");
smp_mb();
return result;
}
#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)
.....
#endif
[0]. 在ARMv6(含v6)架构有了多核的CPU, 为了在多核之间同步数据和控制并发, ARM在内存访问上
增加了独占监测(Exclusive Monitiors)机制, 并增加了相关的ldrex/strex指令, (local monitor & global monitor);
[1]. 对于ARMv6以前的架构是单核的CPU, 对于并发和同步, 对变量的原子访问只需要关闭CPU中断就可以保证原子性;eg.
static inline int atomic_add_return(int i, atomic_t *v) {
unsigned long tmp;
int result;
smp_mb(); // 内存屏障操作 此处暂时不细究
__asm__ __volatile__( // __asm__->嵌套汇编代码, __volatile__->告知编译器不必优化处理
"@ atomic_add_return\n" // @ 在ARM汇编语言中是注释的意思
"1: ldrex %0, [%3]\n" // 1: -> 是一个symbol, 用于下面的"bne 1b"回跳至该处执行,
" add %0, %0, %4\n"
" strex %1, %0, [%3]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter))
: "r" (&r->counter), "Ir" (i)
: "cc"); // "cc"是一个特殊的参数,用来标明汇编代码会修改标志寄存器(flags register)
smp_mb(); // 内存屏障操作 此处不必细究
return result;
}
针对下面的代码进行分析
-------------------------------------------------------------
[line0] __asm__ __volatile__(
[line1] "@ atomic_add_return\n"
[line2] "1: ldrex %0, [%3]\n"
[line3] " add %0, %0, %4\n"
[line4] " strex %1, %0, [%3]\n"
[line5] " teq %1, #0\n"
[line6] " bne 1b"
[line7] : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter))
[line8] : "r" (&r->counter), "Ir" (i)
[line9] : "cc");
-------------------------------------------------------------
[0]: line7 和 line8 可以得到用到的变量
%0 -> result
%1 -> tmp
%2 -> v->counter
%3 -> &(v->counter)
%4 -> i
[1]. line2 独占式加载v->counter数值, 并标记对该段内存的独占访问;
[2]. line3 ==> result = result + i;
[3]. line4 独占式保存v->counter数值, 操作结果(成功/失败)保存在tmp中;
[4]. line5 检测strex的操作是否成功
[5]. line6 strex的操作失败的话,向后跳转到指定标号(jump to 1 label backward)处重新执行