一个全局共享的变量flag
int flag=0
进程A
void funcA()
{
flag++;
}
进程B
void funcB()
{
flag++
}
在进程A和进程B都运行起来后,flag的值应该会是多少?
funcA先执行,再执行funcB。或者 funcB先执行,再执行funcA。 上述无论那个先执行,结果都是2。没有什么多说的。
先说下flag++在汇编级别的代码:
P1. mov %eax $flag //将flag保存到寄存器中
P2. inc %eax //将寄存器中的值加1
P3. mov $flag %eax //将寄存器中的值回写到flag中
如果funcA先执行,当执行完P1指令后,没有执行P2指令时。funcB被处理器调度执行,当funcB从头到尾执行完后,flag等于1。然后回到funcA打断的节点上,继续执行。因为以前执行了P1,导致eax中的值依然是0,所以funcA执行完后,flag还是为1。
对于单CPU当执行系统调用的时候有中断发生,也会出现上述情况。 对于多CPU,如果不是percpu变量,多个CPU共享的,也会出现多个CPU修改同一个值的现象。
针对上述情况,kernel就针对保护一个整型变量提出了原子变量。
Linux源码中定义了一个类型为atomic_t的原子变量。
typedef struct{
int counter;
}atomic_t;
#define CONFIG_64BIT
typedef struct{
long counter;
}atomic64_t;
#endif
从上述定义可以看出,在32位上atomic_t就是一个init型counter, 在64位系统上使用的是long型变量。
接口函数 | 详细说明 |
---|---|
atomic_add(int i, atomic_t *v) | 给原子变量v加i |
atomic_sub(int i, atomic_t *v) | 给原子变量v减i |
atomic_inc(atomic_t *v) | 原子变量v加1 |
atomic_dec(atomic_t *v) | 原子变量v减1 |
atomic_add_return(int i, atomic_t *v) | 给原子变量v加i,并将最新的v返回 |
atomic_sub_return(int i, atomic_t *v) | 给原子变量v减i,并将最新的v返回 |
atomic_read(v) | 获得原子变量的值 |
atomic_set(v, i) | 给原子变量v设置为i |
atomic_cmpxchg(v, old, new) | 比较old和v的值是否相等,如果相等,就把new赋值给v |
__atomic_add_unless(v, a, u) | 如果u不等与c,就将v+a复制给v |
以上是atomic_t绝大多数的原子操作函数集合。这些函数实现都依赖于特定的硬件平台。
/**
* atomic_add - add integer to atomic variable
* @i: integer value to add
* @v: pointer of type atomic_t
*
* Atomically adds @i to @v.
*/
static inline void atomic_add(int i, atomic_t *v)
{
asm volatile(LOCK_PREFIX "addl %1,%0"
: "+m" (v->counter)
: "ir" (i));
}
x86上用带有“LOCK_PREFIX前缀的addl指令来保证原子变量v加1操作的原子性。”LOCK_PREFIX“前缀在x86上的作用在执行add指令时独占系统总线。这样在执行期间,别的进程也无法修改counter的值
static inline int atomic_add_return(int i, atomic_t *v) \
{ \
unsigned long tmp; \
int result; \
\
asm volatile("// atomic_add_return\n" \
"1: ldxr %w0, %2\n" \
" add %w0, %w0, %w3\n" \
" stlxr %w1, %w0, %2\n" \
" cbnz %w1, 1b" \
: "=&r" (result), "=&r" (tmp), "+Q" (v->counter) \
: "Ir" (i) \
: "memory"); \
\
smp_mb(); \
return result; \
}
汇编语言 | C语言 |
---|---|
1: ldxr %w0, %2 | result = v->counter; |
add %w0, %w0, %w3 | result = result + i; |
stlxr %w1, %w0, %2 | v->counter = result; tmp = 设置是否成功; |
cnnz %w1, 1b | if(tmp != 0) goto 1; |
一个全局共享的变量flag
atomic_t flag=ATOMIC_INIT(0);
进程A
void funcA()
{
atomic_inc(&flag);
}
进程B
void funcB()
{
atomic_inc(&flag);
}
这样之后,无论A或者B谁先执行,结果都是2。