原子操作(X86架构)
楔子
首先什么是原子,意味着不可再分。相应地,反映在计算机程序里,那么就会成为一条指令,不存在中间指令,执行过程不会被打断。这样程序在执行时,就能够保证独占访问,避免其它线程访问它。接下来接口的讲解,是以C11标准有#inlcude
已有接口
这里的每个接口都有另一个声明explict防止隐式转换的函数。能够实现的11个操作包括取数、存数、交换、比较交换(CAS)、加法、减法、按位或、按位异或、设置标志位(用来获取一个自旋锁)、清除标志位。
atomic_store(object, desired); // 将desired这个值读取到原子object中
atomic_load(object); // 从原子object中取值,可以用来赋给一个变量
atomic_exchange(object, desired); // 将desired存到object中,并将旧值返回
atomic_compare_exchange_strong(object, expected, desired); // 如果 object 当前的值与 expected 相同,则将 desired 存储到 object 中。如果交换成功,返回 true;否则,将 object 当前的值存储到 expected 指向的内存中,并返回 false。
atomic_compare_exchange_weak(object, expected, desired); // 这个函数发生时,若交换失败,则可能会将object当前的值存储到expected指向的内存中。
atomic_fetch_add(object, operand); // 将object加一个operand再重新赋给object。
atomic_fetch_sub(object, operand); // 将object减小一个operand再重新赋给object。
atomic_fetch_or(object, operand); // 将object按位(bitwire)或操作一个operand再重新赋给object。
atomic_fetch_xor(); // 将object按位(bitwire)异或操作一个operand再重新赋给object。
atomic_flag_test_and_set(); // 将object指向的原子标志设位非零值,并返回标志原来的值,用来获取一个自旋锁
atomic_flag_clear(); // 将object指向的原子标志,清零
GCC嵌入式汇编
GCC嵌入式汇编格式:asm指明是汇编方式,volatile指明不要进行编译器优化,假如这段部分修改的变量值,并不在后续的程序中使用,那么编译器就会移除掉这段代码。所以我们需要使用volatile告诉编译器不要进行优化。
__asm__ __volatile__( // volatile禁止编译器优化
指令部
: output operand
: input operand
: clobber list
);
指令部中数字前加%表示寄存器的样板操作数。@表示注释。指令中可能会出现指令前缀比如lock:这是避免多处理器同时修改内存中的数据。而且lock还能够保证在完成原子操作后,同时刷新多个处理器的缓存,保证一致性。
输出部,输出约束用=开头,表示只写操作数,一般指明输出对象,接着是一个字母表示对操作类型的说明。+表示可读可写,r表示使用通用寄存器,&表示只用于输出。
clobber resgister,这部分主要是要告诉编译器汇编代码所做的修改。一般以"memory"结束,目的是告诉GCC编译器汇编指令改变了内存中的值,强制要求编译器在执行汇编代码前存储所有缓存的值,然后执行结束后重新加载该值。"cc"表示,condition register状态寄存器。
操作类型说明
约束
用途
范围
a
简单寄存器
r16 to r23
b
基指针寄存器对
y,z
d
上寄存器
r16 to r31
e
指针寄存器对
x, y, z
G
浮点常量
0.0
I
6位正整数常量
0 to 63
J
6位负整数常量
-63 to 0
K
整数常量
2
L
整数常量
0
l
低位寄存器
r0 to r15
m
内存地址
M
8位整数常量
0 to 255
n
16位整数常量
N
整数常量
-1
O
整数常量
8, 16, 24
P
整数常量
1
q
堆栈指针寄存器
SPH:SPL
r
任意寄存器
r0 to r31
t
临时寄存器
r0
V
32位整数常量
W
特殊上寄存器对
r24, r26, r28, r30
x
指针寄存器对 X
x (r27:r26)
y
指针寄存器对 Y
y (r29:r28)
z
指针寄存器对 Z
z (r31:r30)
实例
in指令从端口获取数据
__asm__ __volatile__(
"in %0, %1"
:"=&r"(result) // 输出操作数
:"I" (PORTD) // 输入操作数 I表示正整数常量
:
);
inc指令,执行增加add操作
int inc(int* value, int add){
int old;
// volatile指示编译器不要对变量进行优化
__asm__ volatile(
"lock; xaddl %2, %1"
: "=a" (old) // 这里有两个寄存器
: "m" (*value), "a" (add)
: "cc", "memory"
);
}
cas操作
void cas(int *ptr, int expected, int desired) {
__asm__ volatile (
"lock; cmpxchg %2, %0\n" // 使用LOCK前缀的cmpxchg指令
: "+m" (*ptr), "+a" (expected) // 操作数约束
: "r" (desired) // 操作数约束
: "memory" // 标记内存操作
);
}
注意
ubuntu18.04版本内核已经没有/usr/include/asm/atomic.h这个库了,建议直接使用C语言库#Include
参考
《奔跑吧Linux内核》--张天飞
《GCC-AVR Inline Assembler Cookbook》--Harald Kipp