当前位置: 首页 > news >正文

原子变量原理剖析

一、原子操作

原子操作保证指令以原子的方式执行,执行过程不被打断。先看一个实例,如下所示,如果thread_func_a和thread_func_b同时运行,执行完成后,i的值是多少?

`// test.c
static int i = 0;void thread_func_a()
{i++;
}
void thread_func_b()
{i++;
}`

有的读者认为是2,也有的读者认为是1,在给出正确的结果之前,我们先看下这段代码的汇编:

// aarch64-linux-gnu-gcc -S test.c
// vim test.s
.LFB0:.cfi_startprocadrp    x0, iadd     x0, x0, :lo12:i ldr     w0, [x0]        // 加载内存地址为x0寄存器的值,也就是i的值到w0寄存器add     w1, w0, 1       // 将w0寄存器的值与1相加,结果存在w1寄存器adrp    x0, iadd     x0, x0, :lo12:istr     w1, [x0]        // 把w1寄存器的值,加载到x0所在的地址nopret.cfi_endproc
...

可以看到虽然在我们写的代码中,i++只有一条指令,实际上汇编指令需要三条:

  • 加载内存地址的值

  • 修改变量的值

  • 将修改后的值写回原先的地址

两个cpu在执行过程中,顺序是随机的,结果也是随机的,这里为了更直观,给大商家列一下实际可能的执行顺序,以及对应的结果:

可能的结果:i = 2,执行顺序如下:

可能的结果:i = 1,执行顺序如下

针对上面的问题,linux提供了atomic_t类型的原子变量来解决,它可以保证对一个整形数据的原子性。

在内核看来,原子操作函数就像一条汇编语句,保证了操作时不被打断,如上述i++语句就可能被打断,要保证操作的原子性,通常需要原子地(不间断地)完成"读-修改-回写"机制,中间不能被打断。

二、原子变量

linux提供了atomic_t类型的原子变量,它的实现依赖于不同的架构,不同处理器的实现方式不一样。我们首先看下都有哪些原子操作可供使用,然后再针对arm64的实现方式进行解读(其他架构原理都类似,大家自己揣摩)。

2.1 原子操作函数

linux内核提供了很多操作原子变量的函数,了解这些内容,方便我们后续使用。我们以arm64为例进行讲解。

2.1.1 基本原子操作函数

接口:
ATOMIC_INIT(i)
atomic_read(const atomic_t *v)
atomic_set(atomic_t *v, int i)
实现:
// linux-6.9.1/arch/arm64/include/asm/atomic.h#define arch_atomic_read(v)             __READ_ONCE((v)->counter)
#define arch_atomic_set(v, i)           __WRITE_ONCE(((v)->counter), (i))

2.1.2 不带返回值的原子操作函数

接口:
atomic_add(i, v)
atomic_sub(i, v)
atomic_and(i, v)
atomic_or(i, v)
atomic_xor(i, v)
atomic_andnot(i, v)
实现
// linux-6.9.1/arch/arm64/include/asm/atomic.h#define ATOMIC_OP(op)                           \
static __always_inline void arch_##op(int i, atomic_t *v)       \
{                                   \__lse_ll_sc_body(op, i, v);                 \
}ATOMIC_OP(atomic_andnot)
ATOMIC_OP(atomic_or)
ATOMIC_OP(atomic_xor)
ATOMIC_OP(atomic_add)
ATOMIC_OP(atomic_and)
ATOMIC_OP(atomic_sub)

2.1.3 带返回值的原子操作

linux内核提供了两类带返回值的原子操作函数,一类返回原子变量的新值,一类返回原子变量的旧值。 然会原子变量新值的原子操作函数如下。

接口:
atomic_add_return(i, v)
atomic_sub_return(i, v)
实现;
// linux-6.9.1/arch/arm64/include/asm/atomic.h#define ATOMIC_FETCH_OP(name, op)                   \
static __always_inline int arch_##op##name(int i, atomic_t *v)      \
{                                   \return __lse_ll_sc_body(op##name, i, v);            \
}ATOMIC_FETCH_OPS(atomic_add_return)
ATOMIC_FETCH_OPS(atomic_sub_return)
返回原子变量旧值的原子操作函数如下:
接口:
atomic_fetch_add(i, v)
atomic_fetch_sub(i, v)
atomic_fetch_and(i, v)
atomic_fetch_or(i, v)
atomic_fetch_xor(i, v)
atomic_fetch_andnot(i, v)
实现:
// linux-6.9.1/arch/arm64/include/asm/atomic.h
#define ATOMIC_FETCH_OP(name, op)                   \
static __always_inline int arch_##op##name(int i, atomic_t *v)      \
{                                   \return __lse_ll_sc_body(op##name, i, v);            \
}ATOMIC_FETCH_OPS(atomic_fetch_andnot)
ATOMIC_FETCH_OPS(atomic_fetch_or)
ATOMIC_FETCH_OPS(atomic_fetch_xor)
ATOMIC_FETCH_OPS(atomic_fetch_add)
ATOMIC_FETCH_OPS(atomic_fetch_and)
ATOMIC_FETCH_OPS(atomic_fetch_sub)
ATOMIC_FETCH_OPS(atomic_add_return)
ATOMIC_FETCH_OPS(atomic_sub_return)

3.1.4 内嵌内存屏障的原子操作函数

接口:

{}_relexd     // 不内嵌内存屏障原语
{}_acquire    // 内置加载-获取内存屏障原语
{}_release    // 内置存储-释放内存屏障原语
实现:
// linux-6.9.1/arch/arm64/include/asm/atomic.h#define ATOMIC_FETCH_OP(name, op)                   \
static __always_inline int arch_##op##name(int i, atomic_t *v)      \
{                                   \return __lse_ll_sc_body(op##name, i, v);            \
}#define ATOMIC_FETCH_OPS(op)                        \ATOMIC_FETCH_OP(_relaxed, op)                   \ATOMIC_FETCH_OP(_acquire, op)                   \ATOMIC_FETCH_OP(_release, op)                   \ATOMIC_FETCH_OP(        , op)ATOMIC_FETCH_OPS(atomic_fetch_andnot)
ATOMIC_FETCH_OPS(atomic_fetch_or)
ATOMIC_FETCH_OPS(atomic_fetch_xor)
ATOMIC_FETCH_OPS(atomic_fetch_add)
ATOMIC_FETCH_OPS(atomic_fetch_and)
ATOMIC_FETCH_OPS(atomic_fetch_sub)
ATOMIC_FETCH_OPS(atomic_add_return)
ATOMIC_FETCH_OPS(atomic_sub_return)

2.2 原子操作的实现

2.2.1 原子操作的实现

原子操作的实现依赖处理器硬件提供支持,在不同的处理器体系结构上,原子操作会有不同的实现,例如在x86体系结构下,通常使用锁缓存/总线的方式实现原子操作。目前在ARMv8体系结构下支持两种方式来实现原子操作:

  • 一种是经典的独占内存访问机制,也叫做LL/SC(Load-Link/Store-Conditional),早期ARM体系结构下的原子操作都是基于这种方式实现;

  • 另一种是ARMv8.1体系结构上新增的LSE(Large System Extension)扩展,LSE提供了多种原子内存访问操作指令。

具体选择哪一种,CONFIG_ARM64_LSE_ATOMICS决定

// linux-6.9.1/arch/arm64/include/asm/lse.h
#ifdef CONFIG_ARM64_LSE_ATOMICS#define __LSE_PREAMBLE  ".arch_extension lse\n"#include <linux/compiler_types.h>
#include <linux/export.h>
#include <linux/stringify.h>
#include <asm/alternative.h>
#include <asm/alternative-macros.h>
#include <asm/atomic_lse.h>
#include <asm/cpucaps.h>#define __lse_ll_sc_body(op, ...)                   \
({                                  \alternative_has_cap_likely(ARM64_HAS_LSE_ATOMICS) ?     \__lse_##op(__VA_ARGS__) :               \__ll_sc_##op(__VA_ARGS__);              \
})/* In-line patching at runtime */
#define ARM64_LSE_ATOMIC_INSN(llsc, lse)                \ALTERNATIVE(llsc, __LSE_PREAMBLE lse, ARM64_HAS_LSE_ATOMICS)#else   /* CONFIG_ARM64_LSE_ATOMICS */#define __lse_ll_sc_body(op, ...)       __ll_sc_##op(__VA_ARGS__)#define ARM64_LSE_ATOMIC_INSN(llsc, lse)    llsc#endif  /* CONFIG_ARM64_LSE_ATOMICS */
#endif  /* __ASM_LSE_H */

2.2.2 ll/sc方式

LL/SC机制使用多个指令,并且每个处理器都需要实现一个专有监视器,LL/SC机制利用独占内存访问指令和独占监视器共同实现原子操作。首先看下ARMv8体系结构提供的独占内存访问指令。

独占内存访问指令

ARMv8体系结构实现的独占内存访问指令为LDXR/STXR:

  • LDXR:内存独占加载指令,它从内存中以独占方式加载内存地址的值到寄存器中;

  • STXR:内存独占存储指令,它以独占的方式把数据存储到内存中。 LDXR/STXR的指令格式如下:

ldxr    <xt>, [xn | sp] 
stxr    <ws>, <xt>, [xn | sp]

多字节独占内存访问指令

LDXP和STXP指令是多字节独占内存访问指令,一条指令可以独占地加载和存储16字节。

ldxp    <xt1>, <xt2>, [xn | sp]
stxp    <ws>, <xt1>, <xt2>, [<xn | sp>]

独占监视器

独占监视器是一个硬件状态机,用于跟踪读-修改-写序列,并支持Load和Store操作。当CPU执行LDXR指令时,独占监视器会把对应内存地址标记为独占访问模式,保证以独占的方式来访问这个内存地址;而STXR是有条件的存储指令,当CPU执行STRX指令将新数据写入到LDXR指令标记的独占访问内存地址时,会根据独占监视器的状态来进行处理:

  • 若独占监视器为独占访问状态,那么STRX指令执行成功,并且独占监视器会切换状态到开放访问状态;

  • 若独占监视器为开放访问状态,则STRX指令执行失败,数据无法存储。

ARMv8体系提供了三类独占监视器:

  • 本地独占监视器

  • 内部缓存一致性全局独占监视器

  • 外部全局独占监视器

这些独占监视器分别位于系统存储结构的不同层次,如下

atomic_op实现:
// linux-6.9.1/arch/arm64/include/asm/atomic_ll_sc.h
#define ATOMIC_OP(op, asm_op, constraint)               \
static __always_inline void                             \
__ll_sc_atomic_##op(int i, atomic_t *v)                 \
{                                                       \unsigned long tmp;                                  \int result;                                         \\asm volatile("// atomic_" #op "\n"                  \"   prfm    pstl1strm, %2\n"                        \"1: ldxr    %w0, %2\n"                              \"   " #asm_op " %w0, %w0, %w3\n"                    \"   stxr    %w1, %w0, %2\n"                         \"   cbnz    %w1, 1b\n"                              \: "=&r" (result), "=&r" (tmp), "+Q" (v->counter)    \: __stringify(constraint) "r" (i));                 \
}

第11行:将v->counter的值以内存独占加载的方式存储到w0寄存器,即result = v->counter

第12行:将w0的值和i的值操作(add/sub等)结果保存在w0,即result = result + i

第13行:将w0的值写回v->counter,成功的为给w1赋0,否则等于1

第14行:判断temp的值,为0代表成功;为1代表失败,跳转到ldxr。

说白了,这里也是一个自旋

2.2.3 lse方式

在ARMV8.1指令集中增加了一些新的原子操作指令,可以一个指令实现整形运算。

新增的整形原子指令:

接口:
stclr
stset
steor
stadd实现:
#define ATOMIC_OP(op, asm_op)                       \
static __always_inline void                     \
__lse_atomic_##op(int i, atomic_t *v)                   \
{                                   \asm volatile(                           \__LSE_PREAMBLE                          \"   " #asm_op " %w[i], %[v]\n"              \: [v] "+Q" (v->counter)                     \: [i] "r" (i));                         \
}ATOMIC_OP(andnot, stclr)
ATOMIC_OP(or, stset)
ATOMIC_OP(xor, steor)
ATOMIC_OP(add, stadd)

三、总结

本篇文章首先根据一个真实的事例引出原子操作要解决的问题,然后对linux提供的原子操作的众多接口进行了解释说明,最后对arm架构上的两种原子操作的实现方式原理LL/SC、LSE进行了剖析。经过上面的学习,大家应该已经了解原子变量的使用场景以及内部的实现机理。

参考: https://jishuzhan.net/article/1763876122459639809

《奔跑吧,linux内核-卷一基础架构》

《奔跑吧,linux内核-卷二调试与案例分析》

下篇文章,将经典自旋锁进行解读,敬请期待

一个专注于“嵌入式知识分享”、“DIY嵌入式产品”的技术开发人员,关注我,一起共创嵌入式联盟。

相关文章:

原子变量原理剖析

一、原子操作 原子操作保证指令以原子的方式执行&#xff0c;执行过程不被打断。先看一个实例&#xff0c;如下所示&#xff0c;如果thread_func_a和thread_func_b同时运行&#xff0c;执行完成后&#xff0c;i的值是多少&#xff1f; // test.c static int i 0;void thread…...

WebSocket走私实践(附赠LiveGBS监控系统未授权管理员密码重置)

WebSocket走私实践&#xff08;附赠LiveGBS监控系统未授权管理员密码重置&#xff09; 对此&#xff0c;我特别感谢TryHackMe和HackTheBox academy&#xff0c;永远相信和追随英国TryHackMe所教导的网络安全知识,并保持学习 WebSocket走私相关的知识在这里 前段时间学习过htt…...

CentOS 7 和 CentOS Stream 8 的主要区别

更新频率&#xff1a; CentOS 7&#xff1a;传统的稳定版本&#xff0c;主要用于生产环境&#xff0c;更新频率较低&#xff0c;主要包含安全补丁和重要修复。CentOS Stream 8&#xff1a;滚动发布版本&#xff0c;更新更频繁&#xff0c;包含最新的特性和改进。它处于 Fedora …...

基于go1.19的站点模板爬虫

一、go1.19 go1.19是Go语言的一个版本,于2021年8月发布。它带来了许多新的功能和改进,包括但不限于以下方面: 并发性能改进:go1.19引入了新的调度器算法,称为“网状调度器(netlink scheduler)”,它可以更好地处理大量并发任务,在某些情况下提高了系统的并发能力。 垃…...

(单机版)神魔大陆|v0.51.0|冰火荣耀

前言 今天给大家带来一款单机游戏的架设&#xff1a;神魔大陆v0.51.0:冰火荣耀。 如今市面上的资源参差不齐&#xff0c;大部分的都不能运行&#xff0c;本人亲自测试&#xff0c;运行视频如下&#xff1a; (单机版)神魔大陆 下面我将详细的教程交给大家&#xff0c;请耐心阅…...

k8s自动补全工具和UI管理界面

分享两个有利于K8S的工具 目录 分享两个有利于K8S的工具 一、部署Dashboard&#xff08;主节点&#xff09; 介绍 1.1、查看集群状态 1.2、下载yaml文件并运行Dashboard 1.3、部署服务 1.4、创建访问账户、获取token&#xff08;令牌&#xff09; 1.5、浏览器访问Dash…...

内网渗透:内网基础信息收集

Windows&#xff1a; whoami:查看当前当前主机名和登录用户名 whoami /user : 打印当前主机名和输出SID ​ SID的最后一个数字&#xff1a; 1000&#xff1a;普通管理员 500&#xff1a;administrator 501&#xff1a;Guest 516&#xff1a;域控 544&#xff1a;域管理员 net…...

cos符号链提示是什么?TOT呢?

**关于cos符号链提示&#xff08;Chain-of-Symbol Prompting, CoS&#xff09;**&#xff1a; Chain-of-Symbol Prompting&#xff08;CoS&#xff09;是用于大型语言模型&#xff08;LLMs&#xff09;的一种新的提示方法。它旨在解决LLMs在空间场景中的理解和规划问题&#xf…...

docker-compose部署Flink及Dinky

docker-compose部署Flink及Dinky 服务器环境&#xff1a;centos7 1. 配置hosts vim /etc/hostsx.x.x.x jobmanager x.x.x.x taskmanager x.x.x.x dinky-mysql2. 文件目录结构 . ├── conf │ ├── JobManager │ │ ├── flink-conf.yaml │ │ ├── log…...

数字时代的文化革命:Facebook的社会影响

随着数字技术的飞速发展和互联网的普及&#xff0c;社交网络如今已成为人们日常生活中不可或缺的一部分。在众多社交平台中&#xff0c;Facebook作为最大的社交网络之一&#xff0c;不仅连接了全球数十亿用户&#xff0c;更深刻影响了人们的社会互动方式、文化认同和信息传播模…...

66.前端接口调用返回400的错误

错误代码400通常表示由于无效的请求导致服务器无法处理请求。这可能是由于以下原因之一&#xff1a; 1.语法错误&#xff1a;客户端发送的请求可能存在语法错误&#xff0c;例如缺少必需的参数、格式不正确等。 2.未授权&#xff1a;如果API需要认证&#xff0c;而客户端没有提…...

Hadoop 安装与伪分布的搭建

目录 1 SSH免密登录 1.1 修改主机名称 1.2 修改hosts文件 1.3 创建hadoop用户 1.4 生成密钥对免密登录 2 搭建hadoop环境与jdk环境 2.1 将下载好的压缩包进行解压 2.2 编写hadoop环境变量脚本文件 2.3 修改hadoop配置文件&#xff0c;指定jdk路径 2.4 查看环境是否搭建完成 3 …...

网络安全:渗透测试思路.(面试)

网络安全&#xff1a;渗透测试思路.&#xff08;面试&#xff09; 渗透测试&#xff0c;也称为 "pen testing"&#xff0c;是一种模拟黑客攻击的网络安全实践&#xff0c;目的是评估计算机系统、网络或Web应用程序的安全性. 目录&#xff1a; 网络安全&#xff1a;…...

优化堆排序

优化堆排序 堆排序是一种基于比较的排序算法,它利用堆这种数据结构来进行排序。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。堆排序算法分为两个大的步骤:首先将待排序的序列构造成一个最大堆,此时,整个序…...

vue3使用一些组件的方法

iconpark...

OceanBase 4.2.1 离线安装

OceanBase 4.2.1 离线安装 4.2 版本的OceanBase支持一键安装&#xff0c;所以在线版本的安装简单了很多&#xff0c;但在无法连接网络的情况下安装就只能手动离线安装。 注&#xff1a;如下安装过程都是在同一台机器上面进行&#xff0c;也就是只有一个节点&#xff0c;多个节…...

ForkJoin

线程数超过CPU核心数是没有任何意义的【因为要使用CPU密集型运算】 Fork/Join&#xff1a;线程池的实现&#xff0c;体现是分治思想&#xff0c;适用于能够进行任务拆分的 CPU 密集型运算&#xff0c;用于并行计算 任务拆分&#xff1a;将一个大任务拆分为算法上相同的小任务…...

实验2 色彩模式转换

1. 实验目的 ①了解常用的色彩模式&#xff0c;理解色彩模式转换原理&#xff1b; ②掌握Photoshop中常用的颜色管理工具和色彩模式转换方法&#xff1b; ③掌握使用Matlab/PythonOpenCV编程实现色彩模式转换的方法。 2. 实验内容 ①使用Photoshop中的颜色管理工具&#xff…...

AES加密算法及AES-CMAC原理白话版系统解析

本文框架 前言1. AES加密理论1.1 不同AES算法区别1.2 加密过程介绍1.2.1 加密模式和填充方案选择1.2.2 密钥扩展1.2.3分组处理1.2.4多轮加密1.2.4.1字节替换1.2.4.2行移位1.2.4.3列混淆1.2.4.4轮密钥加1.3 加密模式1.3.1ECB模式1.3.2CBC模式1.3.3CTR模式1.3.4CFB模式1.3.5 OFB模…...

24年hvv前夕,微步也要收费了,情报共享会在今年结束么?

一个人走的很快&#xff0c;但一群人才能走的更远。吉祥同学学安全https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247483727&idx1&sndb05d8c1115a4539716eddd9fde4e5c9&scene21#wechat_redirect这个星球&#x1f517;里面已经沉淀了&#xff1a; 《Ja…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法&#xff1a;原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件&#xff0c;如包含恶意代码、敏感数据或欺诈内容的文档&#xff0c;在企业协同办公环境中&#xff08;如Teams、Google Workspace&#xff09;尤为重要。结合大模型技术&…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

在 Spring Boot 项目里,MYSQL中json类型字段使用

前言&#xff1a; 因为程序特殊需求导致&#xff0c;需要mysql数据库存储json类型数据&#xff0c;因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...

vue3 daterange正则踩坑

<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解

进来是需要留言的&#xff0c;先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码&#xff0c;输入的<>当成字符串处理回显到页面中&#xff0c;看来只是把用户输…...

0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化

是不是受够了安装了oracle database之后sqlplus的简陋&#xff0c;无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话&#xff0c;配置.bahs_profile后也能解决上下翻页这些&#xff0c;但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可&#xff0c…...