当前位置: 首页 > 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.xdnf.cn/news/1121275.html

相关文章:

  • 苍穹外卖学习指南(java的一个项目)(老师能运行,但你不行,看这里!!)
  • python的微竞网咖管理系统
  • UI前端与数字孪生结合实践探索:智慧物流的仓储自动化管理系统
  • Java文件操作
  • Reactor 模式详解
  • 【Echarts】 电影票房汇总实时数据横向柱状图比图
  • ubuntu 22.04 anaconda comfyui安装
  • libimagequant windows 编译
  • 云手机常见问题解析:解决延迟、掉线等困扰
  • 机器学习中的朴素贝叶斯(Naive Bayes)模型
  • 新型eSIM攻击技术可克隆用户资料并劫持手机身份
  • Android 16系统源码_窗口动画(一)窗口过渡动画层级图分析
  • 在 Azure Linux 上安装 RustFS
  • 如何保护文件传输安全?文件传输加密
  • 实战:如何创建 AWS RDS 数据库
  • 从“有”到“优”:iPaaS 赋能企业 API 服务治理建设
  • Foundry 私钥管理指南:方法与安全最佳实践
  • 上下文管理器 和 contextlib 模块
  • 深入浅出Kafka Producer源码解析:架构设计与编码艺术
  • VMware 虚拟机装 Linux Centos 7.9 保姆级教程(附资源包)
  • mybatis-plus-jpa-support
  • 常用的OTP语音芯片有哪些?
  • Spring Boot启动原理:从main方法到内嵌Tomcat的全过程
  • Linux 系统下的 Sangfor VDI 客户端安装与登录完全攻略 (CentOS、Ubuntu、麒麟全线通用)
  • Git LFS 操作处理Github上传大文件操作记录
  • 第一章编辑器开发基础第一节绘制编辑器元素_4输入字段(4/7)
  • Redis集群方案——Redis分片集群
  • 《星盘接口4:银河守护者》
  • 小波变换 | Haar 小波变换
  • 浏览器自动化领域的MCP