当前位置: 首页 > 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…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

HTML中各种标签的作用

一、HTML文件主要标签结构及说明 1. <&#xff01;DOCTYPE html> 作用&#xff1a;声明文档类型&#xff0c;告知浏览器这是 HTML5 文档。 必须&#xff1a;是。 2. <html lang“zh”>. </html> 作用&#xff1a;包裹整个网页内容&#xff0c;lang"z…...

Excel 怎么让透视表以正常Excel表格形式显示

目录 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总...