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

ARMv8.1原子操作指令(ll_sc/lse)

简介

ARMv8.1指令集相对于ARMv8指令集添加了不少新的功能,其中有很大的一块功能称作LSE(Large System Extensions),这其中添加了很多平台原生就支持的原子操作指令。

在这之前,如果想实现某个原子操作,必须要使用LL/SC操作,在ARMv8以前的32位系统中使用LDREX和STREX指令,从ARMv8起,它们被改名成了LDXR和STXR。

LL/SC操作本质上是很多CPU核去抢某个内存变量的独占访问,以前ARM主要用来在低功耗设备上运行,CPU核也不会太多,不会存在太大的问题。但是,现在ARM已经往数据中心发展了,几十核的ARM处理器都已经出现了,如果还是大家一起抢可能会存在严重的性能问题。因此,为了支持这种大型系统,在ARMv8.1中特意加入了大量原生原子操作指令。

ARMv8.1中LSE和LL/SC的区别对照表

对比项LSE(Large System Extensions)LL/SC(Load-Link/Store-Conditional)
指令集特性提供了一组新的原子操作指令,如ldaddldclrldset等,直接支持原子操作基于ldxr(独占加载)和stxr(独占存储)指令组合实现原子操作
原子操作实现方式直接使用专门的原子指令完成操作,一条指令即可实现原子性通过ldxr加载数据,进行修改后,再用stxr尝试存储,若存储成功则操作完成,否则需重试
代码复杂度代码编写简单,直接调用对应原子指令即可代码相对复杂,需要手动编写加载、修改、存储及重试逻辑
性能表现在支持LSE的硬件上,由于指令直接由硬件优化实现,通常性能更好因需要多次尝试存储,在高并发竞争激烈场景下,性能可能较差
内存序支持提供多种内存序选项,可满足不同场景下的内存一致性需求内存序支持相对较灵活,但需开发者手动控制,复杂度较高
适用场景适用于对性能要求高、原子操作频繁且硬件支持LSE的场景适用于对代码兼容性要求高、硬件不支持LSE或原子操作不频繁的场景
指令数量新增了多条专门用于原子操作的指令主要依赖ldxrstxr两条核心指令,结合其他逻辑指令实现功能
可移植性由于是ARMv8.1新增特性,依赖特定硬件支持,可移植性相对较差基于较为基础的独占访问机制,可移植性较好,在多种架构上可实现类似功能

ARMv8.1平台下新添加原子操作指令LSE

ARM平台下独占访问指令LDREX和STREX的原理与使用详解-CSDN博客

在ARMv8指令集下,LDREX指令被改名成了LDXR指令,而STREX指令被改名成了STXR指令,功能基本上是一样的,除了添加了一个新的特性。当全局监视器标记的对某段内存的独占访问被清空后,将向所有标记了对该段内存独占访问的CPU核都发送事件,将它们从WFE指令中唤醒,继续执行。

ARM64平台下WFE和SEV相关指令解析_sev指令-CSDN博客 :

在ARMv8指令集中,还添加了一种情况,用来发送事件。当全局监视器标记的对某段内存的独占访问被清空后,将向所有标记了对该段内存独占访问的CPU核都发送事件。也就是说,当系统在多个CPU核上,通过LDREX或者LDXR指令读取某段内存后,系统全局监视器会将该段内存标记为独占(Exclusive),这之后又调用了WFE指令进入低功耗模式了。当系统中又有一个CPU,通过STREX或者STXR指令对该段内存进行了写入,这将清空全局监视器对该段内存的独占标记为,那么系统会自动给前面那些CPU核发送事件,将它们唤醒。

LL_SC指令

todo

LSE指令

在这之前,如果想实现某个原子操作,必须要使用LL/SC操作,在ARMv8以前的32位系统中使用LDREX和STREX指令,从ARMv8起,它们被改名成了LDXR和STXR。

LL/SC操作本质上是很多CPU核去抢某个内存变量的独占访问,以前ARM主要用来在低功耗设备上运行,CPU核也不会太多,不会存在太大的问题。但是,现在ARM已经往数据中心发展了,几十核的ARM处理器都已经出现了,如果还是大家一起抢可能会存在严重的性能问题。因此,为了支持这种大型系统,在ARMv8.1中特意加入了大量原生原子操作指令。

加原子操作

LDADD <Ws>, <Wt>, [<Xn|SP>]
LDADD <Xs>, <Xt>, [<Xn|SP>]STADD <Ws>, [<Xn|SP>]
STADD <Xs>, [<Xn|SP>]

LDADD指令从第三个参数,也就是Xn或SP寄存器指定的内存位置读出32位或64位的值,将其存放进第二个参数,也就是Wt或Xt寄存器中,然后再将这个读出的值和第一个参数,也就是Ws或Xs寄存器中的值相加,再存入第三个参数指定的内存中(*(Xn|SP) += Xs),并且保证这些步骤都是原子的。

STADD指令和LDADD指令基本功能相同,只不过没有第二个参数,也就是Wt或Xt寄存器,不会返回指定内存位置上没修改之前的值。

所以,ST打头的指令和LD打头的指令,基本功能上没有什么区别,只不过LD打头的指令会把在执行该原子指令之前内存中的值存入第二个参数指定的寄存器中,而ST打头的指令没有这个功能。因此,后面只介绍LD打头指令的功能。

置位原子操作

LDSET <Ws>, <Wt>, [<Xn|SP>]
LDSET <Xs>, <Xt>, [<Xn|SP>]STSET <Ws>, [<Xn|SP>]
STSET <Xs>, [<Xn|SP>]

LDSET指令从第三个参数,也就是Xn或SP寄存器指定的内存位置读出32位或64位的值,将其存放进第二个参数,也就是Wt或Xt寄存器中,然后再将这个读出的值和第一个参数,也就是Ws或Xs寄存器中的值进行位的或操作,再存入第三个参数指定的内存中(*(Xn|SP) |= Xs),并且保证这些步骤都是原子的。

清除位原子操作

LDCLR <Ws>, <Wt>, [<Xn|SP>]
LDCLR <Xs>, <Xt>, [<Xn|SP>]STCLR <Ws>, [<Xn|SP>]
STCLR <Xs>, [<Xn|SP>]

LDCLR指令从第三个参数,也就是Xn或SP寄存器指定的内存位置读出32位或64位的值,将其存放进第二个参数,也就是Wt或Xt寄存器中,然后再将这个读出的值和第一个参数,也就是Ws或Xs寄存器中的值取反之后进行位的与操作,再存入第三个参数指定的内存中(*(Xn|SP) &= (NOT Xs)),并且保证这些步骤都是原子的。

异或原子操作

LDEOR <Ws>, <Wt>, [<Xn|SP>]
LDEOR <Xs>, <Xt>, [<Xn|SP>]STEOR <Ws>, [<Xn|SP>]
STEOR <Xs>, [<Xn|SP>]

LDEOR指令从第三个参数,也就是Xn或SP寄存器指定的内存位置读出32位或64位的值,将其存放进第二个参数,也就是Wt或Xt寄存器中,然后再将这个读出的值和第一个参数,也就是Ws或Xs寄存器中的值进行位的异或操作,再存入第三个参数指定的内存中(*(Xn|SP) ^= Xs),并且保证这些步骤都是原子的。

比较存储原子操作

LDSMAX <Ws>, <Wt>, [<Xn|SP>]
LDSMAX <Xs>, <Xt>, [<Xn|SP>]LDUMAX <Ws>, <Wt>, [<Xn|SP>]
LDUMAX <Xs>, <Xt>, [<Xn|SP>]STSMAX <Ws>, [<Xn|SP>]
STSMAX <Xs>, [<Xn|SP>]STUMAX <Ws>, [<Xn|SP>]
STUMAX <Xs>, [<Xn|SP>]

LDSMAX指令从第三个参数,也就是Xn或SP寄存器指定的内存位置读出32位或64位的值,将其存放进第二个参数,也就是Wt或Xt寄存器中,然后再将这个读出的值和第一个参数,也就是Ws或Xs寄存器中的值比较大小,再将大的那个值存入第三个参数指定的内存中(*(Xn|SP) = MAX(*(Xn|SP), Xs)),并且保证这些步骤都是原子的。大小比较的时候,将这个数值作为有符号数比。而LDUMAX指令,顾名思义,和LDSMAX指令功能基本相同,只是比较大小的时候,将这个数值作为无符号数比。

有比较过后将较大的值存入的指令,那就一定会有比较过后将较小的值存入的指令:

LDSMIN <Ws>, <Wt>, [<Xn|SP>]
LDSMIN <Xs>, <Xt>, [<Xn|SP>]LDUMIN <Ws>, <Wt>, [<Xn|SP>]
LDUMIN <Xs>, <Xt>, [<Xn|SP>]STSMIN <Ws>, [<Xn|SP>]
STSMIN <Xs>, [<Xn|SP>]STUMIN <Ws>, [<Xn|SP>]
STUMIN <Xs>, [<Xn|SP>]

交换原子操作

SWP <Ws>, <Wt>, [<Xn|SP>]
SWP <Xs>, <Xt>, [<Xn|SP>]

SWP指令从第三个参数,也就是Xn或SP寄存器指定的内存位置读出32位或64位的值,将其存放进第二个参数,也就是Wt或Xt寄存器中,然后再将第一个参数,也就是Ws或Xs寄存器中的值存入第三个参数指定的内存中,并且保证这些步骤都是原子的。

比较交换原子操作

CAS <Ws>, <Wt>, [<Xn|SP>{,#0}]
CAS <Xs>, <Xt>, [<Xn|SP>{,#0}]CASP <Ws>, <W(s+1)>, <Wt>, <W(t+1)>, [<Xn|SP>{,#0}]
CASP <Xs>, <X(s+1)>, <Xt>, <X(t+1)>, [<Xn|SP>{,#0}]

CAS指令从第三个参数,也就是Xn或SP寄存器指定的内存位置读出32位或64位的值,然后再将这个读出的值和第一个参数,也就是Ws或Xs寄存器中的值进行比较,如果它们相同的话,就把第二个参数,也就是Wt或Xt寄存器中的值存入第三个参数指定的内存中,最后不管前面比较的结果相不相同,都需要将前面读取出来的内存位置的原始值存入第一个参数指定的寄存器中,并且保证这些步骤都是原子的。

CASP也是比较交换原子操作,多出来的P表示Pair。和CAS不同的是,它一次性操作两个连续成对的寄存器。

前面介绍的都是基本的原子操作,操作的寄存器都是32位或64位的,并且没有任何内存屏障的语义。

在上面的基本操作基础上,ARMv8.1还提供了带Load-Acquire或Store-Release单向内存屏障语义的指令。具体来说,如果想在一条基本的原子操作指令上加上Load-Acquire语义,可以在基本指令后面加上A;而如果想在一条基本的原子操作指令上加上Store-Release语义,可以在基本指令后面加上L;还可以两个都加,可以在基本指令后面同时加上AL,那就等同于一个数据内存屏障。

例如,对于LDADD指令来说,有如下自带内存屏障语义的版本:

LDADDA <Xs>, <Xt>, [<Xn|SP>]
LDADDAL <Xs>, <Xt>, [<Xn|SP>]
LDADDL <Xs>, <Xt>, [<Xn|SP>]

但是,对于以ST打头的指令,由于它们不会返回从内存中读取出来的值,所以不需要Load-Acquire语义,就没有包含L的版本。

例如,对于STADD指令,只提供下面一个带Store-Release的版本:

STADDL <Xs>, [<Xn|SP>]


AI写代码
还有,前面说的基本指令都是操作32位或64位数的,如果想操作16位的数,需要在基本指令后面加上H(Halfword);而如果想操作8位的数,需要在基本指令后面加上B(Byte)。

如果原子操作指令又要包含Load-Acquire或Store-Release单向内存屏障语义,又要操作8位或16位的数,那么在基本原子操作指令的后面,先添加表示单向内存屏障语义的A或L,后添加表示操作数位数的B或H。

例如,还是对于基本的LDADD指令,如果想操作8位的数,则有如下版本:

LDADDB <Ws>, <Wt>, [<Xn|SP>]
LDADDAB <Ws>, <Wt>, [<Xn|SP>]
LDADDLB <Ws>, <Wt>, [<Xn|SP>]
LDADDALB <Ws>, <Wt>, [<Xn|SP>]

http://www.dtcms.com/a/278521.html

相关文章:

  • #Paper Reading# Apple Intelligence Foundation Language Models
  • 【Linux网络】:HTTP(应用层协议)
  • 深入解析 Transformer:开启自然语言处理新时代的革命性模型
  • uni-app在安卓设备上获取 (WIFI 【和】以太网) ip 和 MAC
  • 游戏框架笔记
  • SAP ERP与微软ERP dynamics对比,两款云ERP产品有什么区别?
  • [个人笔记] WSL 完整使用指南及 Claude Code 配置记录
  • 019_工具集成与外部API调用
  • 【HarmonyOS】元服务概念详解
  • ubuntu系统在线安装postgres
  • 【视频格式转换】.264格式转为mp4格式
  • React Three Fiber 实现 3D 模型视图切换、显隐边框、显隐坐标轴
  • R 语言科研绘图第 64 期 --- 哑铃图
  • Python 基础语法2:组合数据类型、异常
  • Kafka——集群核心参数配置
  • TensorFlow深度学习实战(26)——生成对抗网络详解与实现
  • [Dify]-进阶1- Dify 支持的多种 AI 模型解析与选择建议
  • 排序算法(二):插入排序
  • 数据安全防护技术:筑牢信息安全防线
  • IoC容器深度解析:架构、原理与实现
  • 区块链开发协作工具全景图:从智能合约管理到去中心化治理
  • LabVIEW浏览器ActiveX事件交互
  • Oracle物化视图函数使用注意事项
  • 新型eSIM攻击技术可克隆用户资料并劫持手机身份
  • AWS RDS PostgreSQL可观测性最佳实践
  • crawl4ai--bitcointalk爬虫实战项目
  • ubuntu安装kafka(无zk版本)
  • Leaflet面试题及答案(81-100)
  • linux打包固件shell脚本
  • 打开xmind文件出现黑色