一文了解原子操作

一文了解原子操作

原子操作(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默认的头文件搜索路径下。这是因为在Linux 2.6.18之后,系统不再使用 atomic.h 和 bitops.h,而是采用GCC提供的内建原子操作函数 __sync_* 来实现原子操作。其实内部还是有atomic.h这个文件,内核源码的arch目录下。

参考

《奔跑吧Linux内核》--张天飞

《GCC-AVR Inline Assembler Cookbook》--Harald Kipp

相关推荐

手机同步软件好选择 91豌豆荚对比实测
bat365在线平台

手机同步软件好选择 91豌豆荚对比实测

📅 09-19 👁️ 7015
【传】开头的成语
365bet亚洲投注网址

【传】开头的成语

📅 11-05 👁️ 7004
美职联足球比赛赛程结果排名
bat365在线平台

美职联足球比赛赛程结果排名

📅 07-03 👁️ 6860