原子变量原理剖析
一、原子操作
原子操作保证指令以原子的方式执行,执行过程不被打断。先看一个实例,如下所示,如果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嵌入式产品”的技术开发人员,关注我,一起共创嵌入式联盟。
相关文章:
原子变量原理剖析
一、原子操作 原子操作保证指令以原子的方式执行,执行过程不被打断。先看一个实例,如下所示,如果thread_func_a和thread_func_b同时运行,执行完成后,i的值是多少? // test.c static int i 0;void thread…...
WebSocket走私实践(附赠LiveGBS监控系统未授权管理员密码重置)
WebSocket走私实践(附赠LiveGBS监控系统未授权管理员密码重置) 对此,我特别感谢TryHackMe和HackTheBox academy,永远相信和追随英国TryHackMe所教导的网络安全知识,并保持学习 WebSocket走私相关的知识在这里 前段时间学习过htt…...
CentOS 7 和 CentOS Stream 8 的主要区别
更新频率: CentOS 7:传统的稳定版本,主要用于生产环境,更新频率较低,主要包含安全补丁和重要修复。CentOS Stream 8:滚动发布版本,更新更频繁,包含最新的特性和改进。它处于 Fedora …...
基于go1.19的站点模板爬虫
一、go1.19 go1.19是Go语言的一个版本,于2021年8月发布。它带来了许多新的功能和改进,包括但不限于以下方面: 并发性能改进:go1.19引入了新的调度器算法,称为“网状调度器(netlink scheduler)”,它可以更好地处理大量并发任务,在某些情况下提高了系统的并发能力。 垃…...
(单机版)神魔大陆|v0.51.0|冰火荣耀
前言 今天给大家带来一款单机游戏的架设:神魔大陆v0.51.0:冰火荣耀。 如今市面上的资源参差不齐,大部分的都不能运行,本人亲自测试,运行视频如下: (单机版)神魔大陆 下面我将详细的教程交给大家,请耐心阅…...
k8s自动补全工具和UI管理界面
分享两个有利于K8S的工具 目录 分享两个有利于K8S的工具 一、部署Dashboard(主节点) 介绍 1.1、查看集群状态 1.2、下载yaml文件并运行Dashboard 1.3、部署服务 1.4、创建访问账户、获取token(令牌) 1.5、浏览器访问Dash…...
内网渗透:内网基础信息收集
Windows: whoami:查看当前当前主机名和登录用户名 whoami /user : 打印当前主机名和输出SID SID的最后一个数字: 1000:普通管理员 500:administrator 501:Guest 516:域控 544:域管理员 net…...
cos符号链提示是什么?TOT呢?
**关于cos符号链提示(Chain-of-Symbol Prompting, CoS)**: Chain-of-Symbol Prompting(CoS)是用于大型语言模型(LLMs)的一种新的提示方法。它旨在解决LLMs在空间场景中的理解和规划问题…...
docker-compose部署Flink及Dinky
docker-compose部署Flink及Dinky 服务器环境: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的社会影响
随着数字技术的飞速发展和互联网的普及,社交网络如今已成为人们日常生活中不可或缺的一部分。在众多社交平台中,Facebook作为最大的社交网络之一,不仅连接了全球数十亿用户,更深刻影响了人们的社会互动方式、文化认同和信息传播模…...
66.前端接口调用返回400的错误
错误代码400通常表示由于无效的请求导致服务器无法处理请求。这可能是由于以下原因之一: 1.语法错误:客户端发送的请求可能存在语法错误,例如缺少必需的参数、格式不正确等。 2.未授权:如果API需要认证,而客户端没有提…...
Hadoop 安装与伪分布的搭建
目录 1 SSH免密登录 1.1 修改主机名称 1.2 修改hosts文件 1.3 创建hadoop用户 1.4 生成密钥对免密登录 2 搭建hadoop环境与jdk环境 2.1 将下载好的压缩包进行解压 2.2 编写hadoop环境变量脚本文件 2.3 修改hadoop配置文件,指定jdk路径 2.4 查看环境是否搭建完成 3 …...
网络安全:渗透测试思路.(面试)
网络安全:渗透测试思路.(面试) 渗透测试,也称为 "pen testing",是一种模拟黑客攻击的网络安全实践,目的是评估计算机系统、网络或Web应用程序的安全性. 目录: 网络安全:…...
优化堆排序
优化堆排序 堆排序是一种基于比较的排序算法,它利用堆这种数据结构来进行排序。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。堆排序算法分为两个大的步骤:首先将待排序的序列构造成一个最大堆,此时,整个序…...
vue3使用一些组件的方法
iconpark...
OceanBase 4.2.1 离线安装
OceanBase 4.2.1 离线安装 4.2 版本的OceanBase支持一键安装,所以在线版本的安装简单了很多,但在无法连接网络的情况下安装就只能手动离线安装。 注:如下安装过程都是在同一台机器上面进行,也就是只有一个节点,多个节…...
ForkJoin
线程数超过CPU核心数是没有任何意义的【因为要使用CPU密集型运算】 Fork/Join:线程池的实现,体现是分治思想,适用于能够进行任务拆分的 CPU 密集型运算,用于并行计算 任务拆分:将一个大任务拆分为算法上相同的小任务…...
实验2 色彩模式转换
1. 实验目的 ①了解常用的色彩模式,理解色彩模式转换原理; ②掌握Photoshop中常用的颜色管理工具和色彩模式转换方法; ③掌握使用Matlab/PythonOpenCV编程实现色彩模式转换的方法。 2. 实验内容 ①使用Photoshop中的颜色管理工具ÿ…...
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前夕,微步也要收费了,情报共享会在今年结束么?
一个人走的很快,但一群人才能走的更远。吉祥同学学安全https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247483727&idx1&sndb05d8c1115a4539716eddd9fde4e5c9&scene21#wechat_redirect这个星球🔗里面已经沉淀了: 《Ja…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
