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

卷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.hqwc.cn/news/164499.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Servlet详解

一.Servlet生命周期 初始化提供服务销毁 1.测试生命周期 package com.demo.servlet;import javax.servlet.*; import java.io.IOException;public class LifeServlet implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {…

ros的安装和rosdep的初始化操作

ros的安装 安装ROS完整桌面版安装&#xff08;Desktop-Full&#xff09;环境配置rosdep初始化 安装ROS完整桌面版安装&#xff08;Desktop-Full&#xff09; 官网 选择思想者乌龟 选择ubuntu平台 操作 开始安装ROS 完整桌面版安装&#xff08;Desktop-Full&#xff0c;推荐…

小白学爬虫:手机app分享商品短连接获取淘宝商品链接接口|淘宝淘口令接口|淘宝真实商品链接接口|淘宝商品详情接口

通过手机APP分享的商品短链接&#xff0c;我们可以调用相应的接口来获取淘口令真实URL&#xff0c;进而获取到PC端的商品链接及商品ID。具体步骤如下&#xff1a; 1、通过手机APP分享至PC端的短链接&#xff0c;调用“item_password”接口。 2、该接口将返回淘口令真实URL。 3…

如何规划并新建大数据平台的独立生产域?5步走

一般来说&#xff0c;大数据平台包括以下4类数据生产域——生产生态环境&#xff08;正式生产环境&#xff09;、开发和测试环境、培训和演示环境、灾备环境。各生产域在由平台提供资源、安全、监控、故障恢复等保障的同时&#xff0c;不同的生产域之间还需要严格隔离&#xff…

基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python 计算机竞赛

文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于生成对抗网络的照片上色动态算法设计与实现 该项目较为新颖&am…

Qt OpenMP使用

1、概念 OpenMP是一种用于共享内存并行系统的多线程程序设计方案&#xff0c;支持的编程语言包括C、C和Fortran。OpenMP提供了对并行算法的高层抽象描述&#xff0c;特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令&#xff0c;自动将程序并行处理&…

Android transform旋转rotate圆角矩形图roundedCorners,Kotlin

Android transform旋转rotate圆角矩形图roundedCorners&#xff0c;Kotlin import android.graphics.Bitmap import android.os.Bundle import android.util.Log import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import com.bumptech.glide.…

Antd Procomponent 之 proForm - 高级表单

本文作者系360奇舞团前端开发工程师 ProForm 在原来的 Form 基础上增加一些语法糖和更多的布局设置&#xff0c;帮助我们快速的开发一个表单。同时添加一些默认行为&#xff0c;让我们的表单默认好用。分步表单&#xff0c;Modal 表单&#xff0c;Drawer 表单&#xff0c;查询表…

Leetcode 第 369 场周赛题解

Leetcode 第 369 场周赛题解 Leetcode 第 369 场周赛题解题目1&#xff1a;2917. 找出数组中的 K-or 值思路代码复杂度分析 题目2&#xff1a;2918. 数组的最小相等和思路代码复杂度分析 题目3&#xff1a;2919. 使数组变美的最小增量运算数思路代码复杂度分析 题目4&#xff1…

腾讯、巨量等头部营销平台方法论

媒体、营销与市场生态正处于新一轮变革期&#xff0c;尤其是进入移动互联网时代后&#xff0c;行业话语权由创意人转向互联网人&#xff0c;营销的风向与规则&#xff0c;也越来越由掌握流量与资源的头部平台引领。 巨变之下&#xff0c;企业只有从本质层面&#xff0c;认清变…

最全的软件测试面试题(上)

1.熟悉软件测试流程&#xff0c;能够独立完成软件测试相关工作 软件测试流程&#xff1a;首先由公司高层进行立项&#xff0c;产品给出产品说明书&#xff0c;需求人员进行需求分析&#xff0c;接着进行需求评审&#xff08;参加人员&#xff1a;项目组里的人&#xff0c;产品&…

Samtec连接器技术前沿 | 全新对准功能确保测试和测量应用中的精确对准

【摘要/前言】 Samtec开发了一种创新的易于使用的对准技术&#xff0c;以确保测试和测量应用中的精密、高频压缩安装连接器的峰值性能。下面解释了我们所看到的趋势&#xff0c;并概述了我们针对出现的常见对准挑战所开发的解决方案。 【问题所在】 随着数据传输率的不断提高…