原子操作
-
软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
-
开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链
目录
2、原子操作
Linux内核中,原子操作有两种方式:原子变量与原子位
2.1 原子变量在内核中的体现
-
原子变量的定义
在内核中原子变量实际为一个结构体,在
include\linux\types.h
typedef struct { int counter; } atomic_t; #ifdef CONFIG_64BIT typedef struct { long counter; } atomic64_t; #endif
-
原子变量的操作函数
具体的定义在
arch\arm\include\asm\atomic.h
与include\linux\atomic.h
函数 作用 atomic_read(atomic_t *v)
读出原子变量的值,即 v->counter atomic_set(atomic_t *v, int i)
设置原子变量的值,即 v->counter = i atomic_inc(atomic_t *v)
v->counter**++** atomic_dec(atomic_t *v)
v->counter**–** atomic_add(int i,atomic_t * v)
v->counter += i atomic_sub(int i,atomic_t * v)
v->counter -= i atomic_inc_and_test(atomic_t * v)
先加 1,再判断新值是否等于 0;等于 0 的话,返回值为 1 atomic_dec_and_test(atomic_t * v)
先减 1,再判断新值是否等于 0;等于 0 的话,返回值为 1 atomic_sub_and_test(int i, atomic_t * v)
先减i,再判断新值是否为0:是则返回1,否则返回-1; atomic_sub_return(int i, atomic_t * v)
先减i,并返回指向v的指针 atomic_add_return(int i, atomic_t * v)
先加i,并返回指向v的指针 atomic_add_negative(int i, atomic_t * v)
先加i,在判断结果是否为负数:是则返回1,否则返-
2.2 原子变量的内核实现
2.2.1 ATOMIC_OPS 在 UP 系统中的实现
UP 即 Uni-Processor,系统只有一个单核 CPU。
在arch\arm\include\asm\atomic.h
文件中,对于ARMv6以下的架构(即不支持多核操作系统),原子实现如下:
- 关中断
- 进行相关原子变量的操作
- 恢复中断(恢复后中断可能是打开或关闭)
对于上述的操作最终都是通过汇编指令完成,对于单条汇编指令就是原子操作;
- ##:是一种运算符,是将两个运算对象连接在一起,只能出现在带参宏定义的替换文本中
#define ATOMIC_OPS(op, c_op, asm_op)
ATOMIC_OP(op, c_op, asm_op)
ATOMIC_OP_RETURN(op, c_op, asm_op)
ATOMIC_FETCH_OP(op, c_op, asm_op)
#ifdef CONFIG_SMP
#error SMP not supported on pre-ARMv6 CPUs
#endif
#define ATOMIC_OP(op, c_op, asm_op)
static inline void atomic_##op(int i, atomic_t *v)
{
unsigned long flags;
raw_local_irq_save(flags);
v->counter c_op i;
raw_local_irq_restore(flags);
}
#define ATOMIC_OP_RETURN(op, c_op, asm_op)
static inline int atomic_##op##_return(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags);
v->counter c_op i;
val = v->counter;
raw_local_irq_restore(flags);
return val;
}
#define ATOMIC_FETCH_OP(op, c_op, asm_op)
static inline int atomic_fetch_##op(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags);
val = v->counter;
v->counter c_op i;
raw_local_irq_restore(flags);
return val;
}
以ATOMIC_OP(add, +=, add)
为例子,进行上述的宏展开
#define ATOMIC_OPS(add, +=, add)
ATOMIC_OP(add, +=, add)
ATOMIC_OP_RETURN(add, +=, add)
ATOMIC_FETCH_OP(add, +=, add)
#define ATOMIC_OP(add, +=, add)
static inline void atomic_add(int i, atomic_t *v)
{
unsigned long flags;
raw_local_irq_save(flags); ## ------> 关中断
v->counter += i;
raw_local_irq_restore(flags); ## ------> 恢复中断
}
#define ATOMIC_OP_RETURN(add, +=, add)
static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags); ## ------> 关中断
v->counter += i;
val = v->counter;
raw_local_irq_restore(flags); ## ------> 恢复中断
return val;
}
#define ATOMIC_FETCH_OP(add, +=, add)
static inline int atomic_fetch_add(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags);
val = v->counter; ## ------> 关中断
v->counter += i;
raw_local_irq_restore(flags); ## ------> 恢复中断
return val;
}
2.2.2 ATOMIC_OPS 在 SMP 系统中的实现
SMP 就是 Symmetric Multi-Processors,对称多处理器;
在arch\arm\include\asm\atomic.h
文件中,对于ARMv6及以上的架构,原子实现如下:(存在不断循环重复的问题,即自旋)
- 读出
- 修改
- 写入
- 判断
- 重做
#define ATOMIC_OP(op, c_op, asm_op)
static inline void atomic_##op(int i, atomic_t *v)
{
unsigned long tmp;
int result;
prefetchw(&v->counter);
__asm__ __volatile__(
"@ atomic_" #op "\n"
"1: ldrex %0, [%3]\n"
" " #asm_op " %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"
);
}
#define ATOMIC_OP_RETURN(op, c_op, asm_op)
static inline int atomic_##op##_return_relaxed(int i, atomic_t *v)
{
unsigned long tmp;
int result;
prefetchw(&v->counter);
__asm__ __volatile__(
"@ atomic_" #op "_return\n"
"1: ldrex %0, [%3]\n"
" " #asm_op " %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"
);
return result;
}
#define ATOMIC_FETCH_OP(op, c_op, asm_op)
static inline int atomic_fetch_##op##_relaxed(int i, atomic_t *v)
{
unsigned long tmp;
int result, val;
prefetchw(&v->counter);
__asm__ __volatile__(
"@ atomic_fetch_" #op "\n"
"1: ldrex %0, [%4]\n"
" " #asm_op " %1, %0, %5\n"
" strex %2, %1, [%4]\n"
" teq %2, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (val), "=&r" (tmp), "+Qo" (v->counter)
: "r" (&v->counter), "Ir" (i)
: "cc"
);
return result;
}
以ATOMIC_OP(add, +=, add)
为例子,把第一个函数进行宏展开后得到
static inline void atomic_add(int i, atomic_t *v)
{
unsigned long tmp;
int result;
prefetchw(&v->counter);
__asm__ __volatile__(
"@ atomic_" "add" "\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"
);
}
进行执行arm-linux-gcc -S main.c -o main.i
获得汇编码如下:
@ atomic_add1:
ldrex r0, [r2] ## 1. 读出
add r0, r0, r3 ## 2. 修改
strex ip, r0, [r2] ## 3. 写入
teq ip, #0 ## 4. 中途被别人先修改了?
bne 1b ## 5. 那就重做一次
-
读出
ldrex r0, [r2]
读取 r2 所指内存的数据,存入 r0;并且标记 r2 所指内存为“独占访问”。
如果有其他程序再次执行“ ldrex r0, [r2]”,一样会成功,一样会标记 r2 所指内存为“独占访问”。 -
修改r0的值
-
写入
strex ip, r0, [r2]
-
如果 r2 的“独占访问”标记还存在,则把 r0 的新值写入 r2所指内存, 并且清除“独占访问”的标记,
把 ip设为 0 表示成功。 -
如果 r2 的“独占访问”标记不存在了,就不会更新内存,
并且把 ip设为 1 表示失败。
-
-
判断,打断则重做(ip ?= 0)
-
重做(如果 ip ≠ 0)
2.3 原子位在内核中的体现
原子位就是操作原子变量的某一位,操作函数在arch\arm\include\asm\bitops.h
文件内
函数名 | 作用 |
---|---|
set_bit(int nr, volatile unsigned long * p) | 设置(*p)的 bit nr 为 1 |
clear_bit(int nr, volatile unsigned long * p) | 清除(*p)的 bit nr 为 0 |
change_bit(int nr, volatile unsigned long * p) | 改变(*p)的 bit nr,从 1 变为 0,或是从 0 变为 1 |
test_and_set_bit(int nr, volatile unsigned long * p) | 设置(*p)的 bit nr 为 1,返回该位的老值 |
test_and_clear_bit(int nr, volatile unsigned long * p) | 清除(*p)的 bit nr 为 0,返回该位的老值 |
test_and_change_bit(int nr, volatile unsigned long * p) | 改变(*p)的 bit nr,从 1 变为 0,或是从 0 变为 1;返回该位的老值 |
2.4 原子位在内核中的实现
2.4.1 原子位在UP系统中的实现
与原子变量相似,原理为:
- 关中断
- 进行相关原子位的操作
- 恢复中断(恢复后中断可能是打开或关闭)
初始宏为:
#ifndef CONFIG_SMP/* * The __* form of bitops are non-atomic and may be reordered. */
#define ATOMIC_BITOP(name,nr,p)
(__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p))
#else
#define ATOMIC_BITOP(name,nr,p) _##name(nr,p)#endif
以set_bit
为原型进行宏展开:
#define set_bit(nr,p)
ATOMIC_BITOP(set_bit,nr,p)// 如下:(_builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0)
(__builtin_constant_p(nr) ? ____atomic_set_bit(nr, p) : _set_bit(nr,p))
以____atomic_set_bit
为原型分析:
static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)
{
unsigned long flags;
unsigned long mask = BIT_MASK(bit);
p += BIT_WORD(bit);
raw_local_irq_save(flags); // --------------> 关中断
*p |= mask;
raw_local_irq_restore(flags); // --------------> 恢复中断
}
2.4.2 原子位在SMP系统中的实现
与原子变量相似,原理为:(存在不断循环重复的问题,即自旋)
- 读出
- 修改
- 写入
- 判断
- 重做
同样以set_bit
为例子,进行宏展开:
_set_bit(nr,p)
该定义在arch\arm\lib\setbit.S
中
bitop _set_bit, orr
实现在arch\arm\lib\bitops.h
中
#if __LINUX_ARM_ARCH__ >= 6
.macro bitop, name, instrENTRY( \name )UNWIND( .fnstart )
ands ip, r1, #3 strneb r1, [ip]
@ assert word-aligned
mov r2, #1
and r3, r0, #31
@ Get bit offset
mov r0, r0,
lsr #5
add r1, r1, r0, lsl #2
@ Get word offset
#if __LINUX_ARM_ARCH__ >= 7 && defined(CONFIG_SMP)
.arch_extension mp ALT_SMP(W(pldw) [r1]) ALT_UP(W(nop))
#endif
mov r3, r2, lsl r31:
ldrex r2, [r1] ## --------------> 读出
instr r2, r2, r3 ## --------------> 操作
strex r0, r2, [r1] ## --------------> 写入
cmp r0, #0 ## --------------> 比较
bne 1b ## --------------> 重新
bx lrUNWIND( .fnend )ENDPROC(\name ) .endm