ARM64 linux并发与同步之原子操作

news2024/11/24 2:33:29

卷2:调试与案例分析

第一章 并发与同步

画了两张简图,方便理解,如下:
在这里插入图片描述
在这里插入图片描述
针对并发源的问题,我接触的项目中都是SMP系统,目前大部分也都是SMP系统;
对于SMP系统,情况会更复杂。
□ 同一类型的中断处理程序不会并发执行,但是不同类型的中断可能送达不同的CPU, 因此不同类型的中断处理程序可能会并发执行。
□ 同一类型的软中断会在不同的CPU上并发执行。
□ 同一类型的tasklet是串行执行的,不会在多个CPU上并发执行。
□ 不同CPU上的进程上下文会并发执行。

一般实际项目开发中,需要考虑的问题,书中有写到几种场景,也是比较容易忽略的:

  1. 进程呈下文在操作某个临界区中的资源时发生了中断, 恰巧在对应中断处理程序中也访问
    了这个资源。如果不使用内核同步机制来保护,那么可能会发生并发访问的bug。
  2. 如果进程上下文正在访问和修改临界区中的资源时发生了抢占调度,可能会发生并发访问的bug。
  3. 如果在自旋锁的临界区中主动睡眠以让出CPU,那这也可能是一个并发访问的bug。
  4. 如果两个CPU同时修改临界区中的一个资源,那这也可能是一个bug。
  5. 对临界区数据来说需要考虑从中断处理程序、工作线程(worker)处理程序、tasklet处理程序、软中断处理程序等有没有可能并发访问?
  6. 若从当前内核代码路径访问该数据时发生被抢占,被调度、执行的进程会不会访问该数据?
  7. 进程会不会进入睡眠状态以等待该数据?

1.1 原子操作

原子操作是指“原子地"(不间断地)完成’'读-修改-回写”机制,中间不能被打断,保证数据的有效修改;
举个例子:

static int i =0;
//线程A函数

void thread A func()
{
	i++;
}
//线程B函数

void thread B func()
{
	i++;
}

存在数据段中的变量i的结果是多少?有人说是2,有人说不是2;
代码执行过程如下:
在这里插入图片描述
从上面的代码执行过程来看,最终结果可能等于1。因为变量i是临界区的一个,CPU0和CPU1
可能同时访问,发生并发访问。

有的读者认为可以使用加锁的方式,如使用自旋锁来保证i++操作的原子性,但是加锁操作会导致比较大的开销,用在这里有些浪费。杀鸡焉用牛刀!

Linux内核提供了 atomic类型的原子变量,atomic_t类型的具体定义为如下。

<include/linux/types.h>

typedef struct ( 

	int counter; 

} atomic_t;
1. 基本原子操作函数

Linux内核提供最基本的原子操作函数包括atomic_read()函数和atomic_set()函数。

<include/asm-generic/atomic.h>

#define ATOMIC_INIT (i) //原子变量初始化为 i
#define atomic_read (v)  //读取原子变量的值
#define atomic_set (v, i)  //设置变量 v 的值为 i

上述两个函数直接调用READ_ONCE()宏或者WRITE_ONCE()宏来实现,不包括“读-修改-回写”机制,直接使用上述函数容易引发并发访问。

2. 不带返回值的原子操作函数
atomic_inc(v):原子地给v 加 1 
atomic_dec(v):原子地给 v 减 1 
atomic_add(i,v):原子地给 v 加 i
atomic_and(i,v):原子地给v和i做“与”操作。 
atomic_or(i,v):原子地给v和i做"或”操作。 
atomic_xor(i,v):原子地给v和i做“异或”操作。

在这里我不对所有的api接口展开说,有兴趣可以看下下面路径头文件中的API, 讲述了所有关于原子操作的API接口;

kernel/linux/linux-5.15.73/include/linux/atomic/atomic-instrumented.h

接下来我们试图探一下原理:
我们以atomic_add为例

//kernel/linux/linux-5.15.73/include/linux/atomic/atomic-instrumented.h 
atomic_add(int i, atomic_t *v)
{
 	instrument_atomic_read_write(v, sizeof(*v));
 	arch_atomic_add(i, v);
}
kernel/linux/linux-5.15.73/include/asm-generic/atomic.h
#define arch_atomic_add_return			generic_atomic_add_return
#define arch_atomic_sub_return			generic_atomic_sub_return

#define arch_atomic_fetch_add			generic_atomic_fetch_add
#define arch_atomic_fetch_sub			generic_atomic_fetch_sub
#define arch_atomic_fetch_and			generic_atomic_fetch_and
#define arch_atomic_fetch_or			generic_atomic_fetch_or
#define arch_atomic_fetch_xor			generic_atomic_fetch_xor

#define arch_atomic_add				generic_atomic_add
#define arch_atomic_sub				generic_atomic_sub
#define arch_atomic_and				generic_atomic_and
#define arch_atomic_or				generic_atomic_or
#define arch_atomic_xor				generic_atomic_xor
kernel/linux/linux-5.15.73/include/asm-generic/atomic.h 
#ifdef  CONFIG_SMP //多核
#define ATOMIC_OP(op, c_op)						\
static inline void generic_atomic_##op(int i, atomic_t *v)		\
{									\
	int c, old;							\
									\
	c = v->counter;							\
	while ((old = arch_cmpxchg(&v->counter, c, c c_op i)) != c)	\
		c = old;						\
}
#else
#define ATOMIC_OP(op, c_op)						\
static inline void generic_atomic_##op(int i, atomic_t *v)		\
{									\
	unsigned long flags;						\
									\
	raw_local_irq_save(flags);					\
	v->counter = v->counter c_op i;					\
	raw_local_irq_restore(flags);					\
}
#endif

看到如果是多核,最终会调到arch_cmpxchg函数,如果是单核,就调用raw_local_irq_save,保存并禁用本地中断,防止抢占;
继续分析多核调用;

//kernel/linux/linux-5.15.73/arch/arm64/include/asm/cmpxchg.h 

#define arch_cmpxchg(...)		__cmpxchg_wrapper( _mb, __VA_ARGS__) //展开__cmpxchg_wrapper( _mb, &v->counter, c, c c_op i)
#define __cmpxchg_wrapper(sfx, ptr, o, n)				\
({									\
	__typeof__(*(ptr)) __ret;					\
	__ret = (__typeof__(*(ptr)))					\
		__cmpxchg##sfx((ptr), (unsigned long)(o),		\
				(unsigned long)(n), sizeof(*(ptr)));	\
	__ret;								\
})
// 展开__cmpxchg_mb(&v->counter, c, c c_op i, sizeof(&v->counter)), 64位系统sizeof(&v->counter)=8

#define __CMPXCHG_GEN(sfx)						\
static __always_inline unsigned long __cmpxchg##sfx(volatile void *ptr,	\
					   unsigned long old,		\
					   unsigned long new,		\
					   int size)			\
{									\
	switch (size) {							\
	case 1:								\
		return __cmpxchg_case##sfx##_8(ptr, old, new);		\
	case 2:								\
		return __cmpxchg_case##sfx##_16(ptr, old, new);		\
	case 4:								\
		return __cmpxchg_case##sfx##_32(ptr, old, new);		\
	case 8:								\
		return __cmpxchg_case##sfx##_64(ptr, old, new);		\
	default:							\
		BUILD_BUG();						\
	}								\
									\
	unreachable();							\
}

//展开__cmpxchg_case_mb_64(&v->counter, c, c c_op i)

#define __CMPXCHG_CASE(name, sz)			\
static inline u##sz __cmpxchg_case_##name##sz(volatile void *ptr,	\
					      u##sz old,		\
					      u##sz new)		\
{									\
	return __lse_ll_sc_body(_cmpxchg_case_##name##sz,		\
				ptr, old, new);				\
}

//展开 __lse_ll_sc_body(__cmpxchg_case_mb_64, &v->counter, c, c c_op i)

根据上述逐级展开,可以追寻到下面定义,坚持下快找到真相了。

kernel/linux/linux-5.15.73/arch/arm64/include/asm/lse.h
#ifdef CONFIG_ARM64_LSE_ATOMICS

...

#define __lse_ll_sc_body(op, ...)					\
({									\
	system_uses_lse_atomics() ?					\
		__lse_##op(__VA_ARGS__) :				\
		__ll_sc_##op(__VA_ARGS__);				\
})

...

#else	/* CONFIG_ARM64_LSE_ATOMICS */

#define __lse_ll_sc_body(op, ...)		__ll_sc_##op(__VA_ARGS__)

...
#endif	/* CONFIG_ARM64_LSE_ATOMICS */

可以看到如果定义CONFIG_ARM64_LSE_ATOMICS这个宏,走LSE(是否支持大系统扩展),若定义了这个宏走下面LSE流程实现

kernel/linux/linux-5.15.73/arch/arm64/include/asm/atomic_lse.h
#define __CMPXCHG_CASE(w, sfx, name, sz, mb, cl...)			\
static __always_inline u##sz						\
__lse__cmpxchg_case_##name##sz(volatile void *ptr,			\
					      u##sz old,		\
					      u##sz new)		\
{									\
	register unsigned long x0 asm ("x0") = (unsigned long)ptr;	\
	register u##sz x1 asm ("x1") = old;				\
	register u##sz x2 asm ("x2") = new;				\
	unsigned long tmp;						\
									\
	asm volatile(							\
	__LSE_PREAMBLE							\
	"	mov	%" #w "[tmp], %" #w "[old]\n"			\
	"	cas" #mb #sfx "\t%" #w "[tmp], %" #w "[new], %[v]\n"	\
	"	mov	%" #w "[ret], %" #w "[tmp]"			\
	: [ret] "+r" (x0), [v] "+Q" (*(unsigned long *)ptr),		\
	  [tmp] "=&r" (tmp)						\
	: [old] "r" (x1), [new] "r" (x2)				\
	: cl);								\
									\
	return x0;							\
}

w:表示位宽,支持8位、16位、32位以及64位。
sfx: cas指令的位宽后缀,8位宽使用b后缀,16位宽使用h后缀。
name: 表示内存屏障类型,如“acq_”表示支持加载-获取内存屏障原语,“rel_”表示支持存储-释放内存屏障原语,“mb_”表示同时支持加载-获取和存储-释放内存屏障原语。
sz:位宽大小。
mb:组成cas指令的内存屏障后缀,"a"表示加载-获取内存屏障原语,“l” 表示存储释放内存屏障原语,“al"表示同时支持加载-获取和存储-释放内存屏障原语。
c1:内嵌汇编的损坏部。

从上述内嵌汇编可以看到这段内嵌汇编实现了一个包含原子比较并交换(CAS)指令的操作(比较晦涩难懂),看个大概吧;

若没有定义这个宏走下面流程实现

//kernel/linux/linux-5.15.73/arch/arm64/include/asm/atomic_ll_sc.h 

#define __CMPXCHG_CASE(w, sfx, name, sz, mb, acq, rel, cl, constraint)	\
static inline u##sz							\
__ll_sc__cmpxchg_case_##name##sz(volatile void *ptr,			\
					 unsigned long old,		\
					 u##sz new)			\
{									\
	unsigned long tmp;						\
	u##sz oldval;							\
									\
	/*								\
	 * Sub-word sizes require explicit casting so that the compare  \
	 * part of the cmpxchg doesn't end up interpreting non-zero	\
	 * upper bits of the register containing "old".			\
	 */								\
	if (sz < 32)							\
		old = (u##sz)old;					\
									\
	asm volatile(							\
	__LL_SC_FALLBACK(						\
	"	prfm	pstl1strm, %[v]\n"				\
	"1:	ld" #acq "xr" #sfx "\t%" #w "[oldval], %[v]\n"		\
	"	eor	%" #w "[tmp], %" #w "[oldval], %" #w "[old]\n"	\
	"	cbnz	%" #w "[tmp], 2f\n"				\
	"	st" #rel "xr" #sfx "\t%w[tmp], %" #w "[new], %[v]\n"	\
	"	cbnz	%w[tmp], 1b\n"					\
	"	" #mb "\n"						\
	"2:")								\
	: [tmp] "=&r" (tmp), [oldval] "=&r" (oldval),			\
	  [v] "+Q" (*(u##sz *)ptr)					\
	: [old] __stringify(constraint) "r" (old), [new] "r" (new)	\
	: cl);								\
									\
	return oldval;							\
}

从上述内嵌汇编中可以看到使用Idxr和stxr 独占访问指令的组合;

====================================================================================================
接下来我们简单学习下CAS指令和LDXR/STXR独占访问指令

CAS指令:
ARM64处理器提供了比较并交换指令— cas指令。cas指令根据不同的内存屏障属性分成4类:
□ 隐含了加载-获取内存屏障原语。
□ 隐含了存储-释放内存屏障原语。
□ 同时隐含了加载-获取和存储-释放内存屏障原语。
□ 不隐含内存屏障原语。
在这里插入图片描述
在这里插入图片描述

LDXR/STXR独占访问指令:
ARMv8体系结构都提供了独占访问的的指令,在A64指令集中,LDXR指令尝试在内存总线中申请一个独占访问的锁,然后访问一个内存地址。STXR指令会往刚才LDXR指令已经申请独占访问的内存地址中写一个新内容,通常组合使用LDXR和STXR指令来完成一些同步操作;
它们的操作格式如下:

LDXR 指令:

LDXR <Wt>, [<Xn|SP>{, #<imm>}]

<Wt>:目标寄存器,用于存储从内存中加载的值。
[<Xn|SP>{, #<imm>}]:源内存地址,可以通过基址寄存器 <Xn> 或栈指针寄存器 SP 加上可选的偏移量 <imm> 来指定。

STXR 指令:

STXR <Ws>, <Wt>, [<Xn|SP>{, #<imm>}]

<Ws>:用于指示是否成功执行存储操作的结果寄存器。
<Wt>:要存储到内存中的值的寄存器。
[<Xn|SP>{, #<imm>}]:目标内存地址,可以通过基址寄存器 <Xn> 或栈指针寄存器 SP 加上可选的偏移量 <imm> 来指定。

在这两个指令中,Xn 和 SP 分别代表基址寄存器和栈指针寄存器,imm 为可选的偏移量。这些指令允许对内存进行原子操作,并且通常与并发控制相关的算法一起使用,以确保对共享内存的原子性访问。

还有多字节(16字节)独占访问的扩展指令LDXP和STXP,有兴趣可以自行学习;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1180060.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第二证券:特斯拉将推出低价电动汽车?最新消息

当地时间周一&#xff0c;投资者接连上星期五的达观心境&#xff0c;美国三大股指高开高走。美联储主席鲍威尔本周到会活动时将有两次揭穿说话&#xff0c;投资者期望从中得到更多关于本轮加息周期完毕的信号&#xff0c;但也对联邦基金利率可能在更长时期内维持在高水平保持警…

我的创作纪念日——2048天

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

Java并发工具-1-原子操作(Atomic)

一 原子操作之 AtomicInteger 1 概念解释 什么是原子操作呢&#xff1f;所谓原子操作&#xff0c;就是一个独立且不可分割的操作。 AtomicInteger 工具类提供了对整数操作的原子封装。为什么要对整数操作进行原子封装呢&#xff1f; 在 java 中&#xff0c;当我们在多线程情…

龙芯loongarch64服务器编译安装matplotlib

前言 根据我之前的文章介绍&#xff0c;龙芯loongarch64服务器中的很多python依赖包安装有问题&#xff0c;发现其中安装的"matplotlib"就无法正常使用&#xff0c;或报如下错误&#xff1a;ImportError: cannot import name _c_internal_utils from partially initi…

python 中文字符转换unicode及Unicode 编码转换为中文

废话不多说 直接开干 知识点 decode 字节编码可decode为str encode 将字符串转换为bytes类型的对象 (即b为前缀, bytes类型), 即Ascll编码, 字节数组 encode(‘unicode-escape’)可将此str编码为bytes类型, 内容则是unicode形式 decode(‘unicode-escape’)可将内容为unicode形…

智能座舱“卷“疯了!8295不再是最“亮”点,还需要这些顶级配置

前段时间&#xff0c;车圈被两款“极”字辈的新车刷屏&#xff1a;极越01和极氪001 FR。 前者可视作当前自主品牌车企高度智能化水平的“范式”&#xff0c;后者无疑是中国汽车品牌颠覆极限性能的代表作。 同时&#xff0c;这两款车将座舱配置作为卖点进行了详细推送&#xf…

iview table 表格合并单元格

一、如图所示 二、实现方式 表格用提供的span-method属性 <template><Table ref"table" border :span-method"handleSpan" :row-key"true" :columns"tableColumns" :data"tableData"no-data-text"暂无数据&…

NOIP2023模拟12联测33 B. 游戏

NOIP2023模拟12联测33 B. 游戏 文章目录 NOIP2023模拟12联测33 B. 游戏题目大意思路code 题目大意 期望题 思路 二分答案 m i d mid mid &#xff0c;我们只关注学生是否能够使得被抓的人数 ≤ m i d \le mid ≤mid 那我们就只关心 a > m i d a > mid a>mid 的房…

HTML的初步学习

HTML HTML 描述网页的骨架, 标签化的语言. HTML 的执行是浏览器的工作,浏览器会解析 html 的内容,根据里面的代码,往页面上放东西,浏览器的工作归根结底,还是以汇编的形式在CPU上执行. 浏览器对于html语法格式的检查没有很严格,即使你写的代码有一些不合规范之处,浏览器也会尽可…

ZZ308 物联网应用与服务赛题第E套

2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 &#xff08;E卷&#xff09; 赛位号&#xff1a;______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等&#xff1b; 2.竞赛任务中所使用的…

系列十九、使用JDK生成HTTPS证书

一、HTTPS概述 历史上&#xff0c;HTTPS 连接经常用于网络上的交易支付和企业信息系统中敏感信息的传输。在 2000 年代末至 2010 年代初&#xff0c;HTTPS 开始广泛使用&#xff0c;以确保各类型的网页真实&#xff0c;保护账户和保护用户通信&#xff0c;身份和网络浏览的私密…

家政预约服务小程序源码系统 线上+线下两种模式 带完整的搭建教程

人们生活水平的不断提高&#xff0c;使得家政服务行业逐渐成为一个重要的行业。然而&#xff0c;传统的家政服务模式存在一些问题&#xff0c;如信息不对称、服务质量不稳定等。为了解决这些问题&#xff0c;开发一款家政预约服务小程序源码系统变得尤为重要。下面源码小编来给…

Unity 声音的控制

闲谈&#xff1a; 游戏开发比普通软件开发难也是有原因的&#xff0c;第一 游戏功能需求变化多样内部逻辑交错纵横&#xff0c; 而软件相对固定&#xff0c;无非也就是点击跳转、数据存储 第二&#xff0c;游戏需要很多3D数学知识、物理知识&#xff0c;最起码得有高中物理的基…

Python开发运维:Python3.7安装Django3.2

目录 一、理论 1.pip 2.Django 3.Pycharm国内镜像源 二、实验 1.Python3.7安装Django3.2 三、问题 1.安装django3.2报错 2.pip更新报错 一、理论 1.pip &#xff08;1&#xff09;概念 1&#xff09;pip pip 是 Python 的包安装程序。其实&#xff0c;pip 就是 Pyt…

应用程序无法启动,因为应用程序的并行配置不正确。有关详细信息,请参阅应用程序事件日志,或使用命令行 sxstrace.exe 工具。

谷歌浏览器出现以下问题 解决 点击以下 new_chrome.exe 就可以了&#xff08; new_chrome.exe 点击之后就消失了&#xff09;

北方寒流来袭,供暖已至,你家的暖气热了吗?

如果说诗句“忽如一夜春风来&#xff0c;千树万树梨花开”来形容春天的到来&#xff0c;那么“夜凉如水&#xff0c;寒风乍起添衣裳”就可以形容现在北方的天气了&#xff0c;11月初的早晨&#xff0c;伴随着萧瑟秋风卷动着枯黄落叶的声音&#xff0c;感觉就像是在落魄时买了一…

HarmonyOS应用开发Tabs组件的使用

Entry Component struct TabsPage {State currentIndex: number 0;private tabsController: TabsController new TabsController();private controller: TabsController new TabsController()/*** 自定义TabBar* param title* param targetIndex* param selectedImg* param …

wx 小程序不打开调试模式无法获取数据

问题开始 最近学习小程序&#xff0c;发布了一个体验版的小程序&#xff0c;发现正常扫码进入后接口数据是无法访问的。也就是原始数据,不过开启调试模式后,数据又一切正常&#xff0c;但是总不能让每个人都开启调试模式用吧&#xff0c;终于查阅资料后找到了解决问题的办法 …

viple入门(三)

&#xff08;1&#xff09;条件循环活动 条件循环活动中&#xff0c;必须给定条件&#xff0c;条件成立&#xff0c;则执行条件循环的后续程序。 条件不成立&#xff0c;则不执行后续程序。 从报错信息来看&#xff0c;程序提示&#xff1a;条件循环要和结束循环活动一起使用。…

抖音大型直播的画质优化实践

面临挑战 随着抖音内容生态的不断丰富&#xff0c;越来越多的大型赛事在抖音平台进行直播&#xff0c;世界杯/春晚/亚运会等各项赛事节目引来大量用户观看。卡塔尔世界杯期间&#xff0c;抖音提供的稳定高质直播画面为观众带来了完美的观赛体验&#xff0c;决赛的 PCU 高达 370…