Linux kernel 原子操作
主要看一下汇编是怎么实现的, 最后发现是通过硬件提供的指令集来完成的。
1- 预取内存数据
2- 原子加载、存储数据
3- 判断 PE 标记, 失败则跳转 2- 循环
内核原子操作
内核使用atomic_t
类型的原子变量,具体的实现依赖于不同的体系架构,我这里是ARM64
的代码进行分析。
atomic_t
类型定义:
typedef struct {
int counter;
} atomic_t;
#ifdef CONFIG_64BIT
typedef struct {
long counter;
} atomic64_t;
#endif
内核提供了许多原子变量的操作函数:可以查看arch/arm64/include/asm/atomic.h
里面的定义。
分析原子汇编代码
随便找一个包含内核atomic.h
头文件的代码进行一个预编译,比如我之前写的一份trace
的代码:
make ARCH=arm64 CROSS_COMPILE=aarch64-himix100-linux- drivers/mj/trace.i
编译之后可以找到里面atomic
相关的预编译之后的函数,这里找一个简单常用的atomic_add()
函数进行分析一下:
里面的知识点比较散,前面的always_inline
以及no_instrument_function
是在gcc
手册的,具体内容可以查看gcc
手册或者上面的简单注释:内联,不生成概要函数分析调用。
arm
嵌入汇编也可以查看一下gcc
手册的6.47 How to Use Inline Assembly Language in C Code
。这里主要将一下参数部分的那几个=
,&
,r
,+
,Q
是什么意思,更详细的内容还是要看gcc
手册的介绍。
比如上面的I
,Q
可能在不同的架构中又不同的定义:
看一下汇编指令的内容,这里要查找对应的数据手册,这里是armv8-a.pdf
的arm
架构参考手册。
prfm 预取指令
预取存储器(寄存器)向存储器系统发出信号,表明在不久的将来很可能发生从指定地址访问数据存储器的事件。内存系统可以通过采取确实会加快内存访问速度的操作来做出响应,例如将包含指定地址的缓存行预加载到一个或多个缓存中。
ldxr 原子的加载操作
加载独占寄存器从基本寄存器值派生地址,从内存加载 32 位单词或 64 位双字,然后写入寄存器。内存访问是原子的。PE 标记将正在访问的物理地址标记为独占访问。
stxr 原子的存储操作
存储独占寄存器存储 32 位单词或 64 位双字从寄存器存储到内存(如果 PE 具有对内存地址的独占访问权限)的状态值,如果存储成功,则返回状态值 0;如果未执行存储,则返回 1 的状态值。
cbnz 非0跳转
非零上的比较和分支比较寄存器中的值为零,如果比较不相等,则有条件地分支为 PC 相对偏移处的标签。它提供了一个提示,提示这不是子例程调用或返回。此指令不会影响条件标志。
Armv8的同步原语
Armv8使用同步原语提供共享内存的非阻塞同步。 本节中有关通过同步原语进行内存访问的信息适用于对普通内存和任何类型的设备内存的访问。
看一下ldaxr
和ldxr
的区别:ldaxr
也是原子操作,但ldaxr
使用的描述说是Load-Acquire
,看一下它给的连接。
是支持发布一直想顺序一致性模式?暂时不理解,但看spin_lock
的代码是使用的这个指令。