linux内存屏障

  • why? 为什么要有内存屏障,内存屏障主要解决什么问题

  • What? 内存屏障都有哪些

  • How? 内存屏障如何使用

本篇文章主要解决前两个问题

一、为什么要有内存屏障

我们都知道计算机运算任务需要CPU和内存相互配合共同完成,其中CPU负责逻辑计算,内存负责数据存储。CPU要与内存进行交互,如读取运算数据、存储运算结果等。

由于内存和CPU的计算速度有几个数量级的差距,为了提高CPU的利用率,现代处理器结构都加入了一层读写速度尽可能接近CPU运算速度的高速缓存来作为内存与CPU之间的缓冲。

将运算需要使用的数据复制到缓存中,让CPU运算可以快速进行,计算结束后再将计算结果从缓存同步到主内存中,这样处理器就无须等待缓慢的内存读写了。就像下面这样。

每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取,但是这样的弊端也很明显:不能实时的和内存发生信息交换,会使得不同CPU执行的不同线程对同一个变量的缓存值不同 。

总结来说,现在大多数现代计算机为了提高性能而采取乱序执行,乱序执行可能会导致程序运行不符合我们预期,这使得内存屏障成为必须。

二、乱序访问

乱序访问分为两类,一类是编译时内存乱序访问,一类是运行时乱序访问。

2.1 编译时内存乱序访问

编译器对代码做出优化时,可能改变实际执行指令的顺序(例如g++下O2或者O3都会改变实际执行指令的顺序),看一个例子

int x, y, r;
void f()
{
    x = r;
    y = 1;
}

首先直接编译次源文件:g++ -S test.cpp。我们得到相关的汇编代码如下:

movl    r(%rip), %eax
movl    %eax, x(%rip)
movl    $1, y(%rip)

这里我们可以看到,x = r和y = 1并没有乱序执行。现使用优化选项O2(或O3)编译上面的代码(g++ -O2 –S test.cpp),生成汇编代码如下:

movl    r(%rip), %eax
movl    $1, y(%rip)
movl    %eax, x(%rip)

我们可以清楚地看到经过编译器优化之后,movl $1, y(%rip)先于movl %eax, x(%rip)执行,这意味着,编译器优化导致了内存乱序访问。

避免此行为的办法就是使用编译器屏障(又叫优化屏障)。 Linux内核提供了函数barrier(),用于让编译器保证其之前的内存访问先于其之后的内存访问完成。

#define barrier() __asm__ __volatile__("": : :"memory")

现在把此编译器barrier加入代码中:再编译,就会发现内存乱序访问已经不存在了。

int x, y, r;
void f()
{
    x = r;
    __asm__ __volatile__("": : :"memory")
    y = 1;
}

2.2 运行时内存乱序访问

运行时,CPU本身是会乱序执行指令的。 早期的处理器为有序处理器(in-order processors),总是按开发者编写的顺序执行指令, 如果指令的输入操作对象(input operands)不可用(通常由于需要从内存中获取), 那么处理器不会转而执行那些输入操作对象可用的指令,而是等待当前输入操作对象可用。

相比之下,乱序处理器(out-of-order processors)会先处理那些有可用输入操作对象的指令(而非顺序执行) 从而避免了等待,提高了效率。

现代计算机上,处理器运行的速度比内存快很多, 有序处理器花在等待可用数据的时间里已可处理大量指令了。即便现代处理器会乱序执行,但在单个CPU上,指令能通过指令队列顺序获取并执行,结果利用队列顺序返回寄存器堆,这使得程序执行时所有的内存访问操作看起来像是按程序代码编写的顺序执行的, 因此内存屏障是没有必要使用的(前提是不考虑编译器优化的情况下)。

SMP架构需要内存屏障的进一步解释:

从体系结构上来看,首先在SMP架构下,每个CPU与内存之间,都配有自己的高速缓存(Cache),以减少访问内存时的冲突

采用高速缓存的写操作有两种模式:

  • 穿透(Write through)模式,每次写时,都直接将数据写回内存中,效率相对较低;

  • 回写(Write back)模式,写的时候先写回告诉缓存,然后由高速缓存的硬件再周转复用缓冲线(Cache Line)时自动将数据写回内存,或者由软件主动地“冲刷”有关的缓冲线(Cache Line)

出于性能的考虑,系统往往采用的是模式2来完成数据写入。 正是由于存在高速缓存这一层,正是由于采用了Write back模式的数据写入,才导致在SMP架构下,对高速缓存的运用可能改变对内存操作的顺序。

以上面的一个简短代码为例:

// thread 0 -- 在CPU0上运行
x = 42;
ok = 1;
 
// thread 1 – 在CPU1上运行
while(!ok);
print(x);

这里CPU1执行时, x一定是打印出42吗?让我们来看看以下图为例的说明:

  • 假设,正好CPU0的高速缓存中有x,此时CPU0仅仅是将x=42写入到了高速缓存中

  • 另外一个ok也在高速缓存中,但由于周转复用高速缓冲线(Cache Line)而导致将ok=1刷回到内存中

  • 此时CPU1首先执行对ok内存的读取操作,他读到了ok为1的结果,进而跳出循环,读取x的内容.

  • 而此时,由于实际写入的x(42)还只在CPU0的高速缓存中,导致CPU1读到的数据为x(17)。

三、内存屏障分类

3.1 编译屏障

编译屏障只是告诉编译器,不要对当前代码进行过度的优化,保证生成的汇编代码的次序与当前高级语言的次序保持一致。编译屏障对CPU执行时产生的重排序没有任何作用。

3.2 写内存屏障

一个写内存屏障可以提供这样的保证,站在系统中的其它组件的角度来看,在屏障之前的写操作看起来将在屏障后的写操作之前发生。

如果映射到上面的例子来说,首先,写内存屏障会对处理器指令重排序做出一些限制,也就是在写内存屏障之前的写入指令一定不会被重排序到写内存屏障之后的写入指令之后。其次,在执行写内存屏障之后的写入指令之前,一定要保证清空当前CPU存储缓冲中的所有写操作,将它们全部“提交”到内存中。这样的话系统中的其它组件(包括别的CPU),就可以保证在看到写内存屏障之后的写入数据之前先看到写内存屏障之前的写入数据。

写内存屏障仅仅限制了CPU对写操作的排序,对加载操作没有任何效果,对其它的指令也没有作用。而且,写内存屏障只是保证在写内存屏障之后的写入操作一定是在写内存屏障之前的写入操作之后被系统其它组件感知,它并不能保证在写内存屏障之前的所有写入操作的顺序,也不能保证在写内存屏障之后的所有写入操作的顺序。

写内存屏障只管自己CPU上的写入操作能够按照一定次序被系统中其它部件感知,但是如果其它部件有缓存将旧数据缓存下来了,这它管不着。这个是下面介绍的读内存屏障要管的事,因此一般写内存屏障要和读内存屏障配对使用。

3.3 读内存屏障

一个读内存屏障可以提供这样的保证,站在系统中其它组件的角度来看,所有在读内存屏障之前的加载操作将在读内存屏障之后的加载操作之前发生。

还是用上面的例子来说明,首先,读内存屏障也会对处理器指令重排做出一些限制,也就是在读内存屏障之前的读取指令一定不会被重排序到读内存屏障之后的读取指令之后。其次,在执行读内存屏障之后的读取指令之前,一定要保证处理完当前CPU的无效队列。这样的话,当前CPU的缓存状态将完全遵照MESI协议,可以保证缓存数据一致性。

读内存屏障仅仅限制了CPU对加载操作的排序,对存储操作没有任何效果,对其它指令也没有任何作用。而且,读内存屏障只是保证在读内存屏障之后的读取操作一定是在读内存屏障之前的读取操作之后才去感知内存数据变化的,它并不能保证读内存屏障之前的所有读取操作顺序,也不能保证读内存屏障之后的所有读取操作的顺序。

读内存屏障只管自己CPU上的读取操作能够按照一定次序去感知系统内存中的值,但是对于其它CPU写入系统内存的次序没有任何约束。这个是上面介绍的写内存屏障要管的事,因此一般读内存屏障要和写内存屏障配对使用。

3.4 通用内存屏障(读写内存屏障)

一个通用内存屏障可以提供这样的保证,站在系统中其它组件的角度来看,通用内存屏障之前的加载、存储操作都将在通用内存屏障之后的加载、存储操作之前发生。

还是用上面的例子来说明,首先,通用内存屏障也会对处理器指令重排做出一些限制,也就是在通用内存屏障之前的写入和读取指令一定不会被重排序到通用内存屏障之后的写入和读取指令之后。其次,在执行通用内存屏障之后的任何写入和读取取指令之前,一定要保证清空当前CPU存储缓冲中的所有写操作,并且还要处理完当前CPU的无效队列。

通用内存屏障等同于同时包含了读和写内存屏障的功能,因此也可以替换它们中的任何一个,只不过可能会一定程度上影响性能。

通用内存屏障同时限制了CPU对加载操作和存储操作的排序,但是对其它指令没有任何作用。而且,通用内存屏障只是保证在通用内存屏障之后的所有写入和读取操作一定是在通用内存屏障之前的写入和读取操作之后才执行,它并不能保证通用内存屏障之前的所有读取和写入操作的顺序,也不能保证通用内存屏障之后的所有读取和写入操作的顺序。

一般写内存屏障、读内存屏障和通用内存屏障都会默认包含编译屏障

四、四、现有架构的内存屏障操作

4.1 x86/64

x86/64系统架构提供了三种内存屏障指令: (1) sfence; (2) lfence; (3) mfence。

// linux-6.9.1/arch/arm64/include/asm/barrier.h
#define __mb()        asm volatile("mfence":::"memory")
#define __rmb()        asm volatile("lfence":::"memory")
#define __wmb()        asm volatile("sfence" ::: "memory")
  • sfence:可以看做是一定将数据写回内存,而不是写到高速缓存中。

  • lfence:可以看做是一定将数据从高速缓存中抹掉,从内存中读出来,而不是直接从高速缓存中读出来。

  • mfence则正好结合了两项操作。

4.2 arm64

arm64系统提供的下面几种内存屏障指令:

#define isb()           asm volatile("isb" : : : "memory")
#define dmb(opt)        asm volatile("dmb " #opt : : : "memory")
#define dsb(opt)        asm volatile("dsb " #opt : : : "memory")

#define __smp_mb()        dmb(ish)
#define __smp_rmb()       dmb(ishld)
#define __smp_wmb()       dmb(ishst)

#define __mb()            dsb(sy)
#define __rmb()           dsb(ld)
#define __wmb()           dsb(st)

#define __dma_mb()        dmb(osh)
#define __dma_rmb()       dmb(oshld)
#define __dma_wmb()       dmb(oshst)

五、总结

现代计算机为了提高性能多采取乱序执行,乱序执行又分为编译乱序和执行乱序两类,前者是编译器的行为,后者是cpu运行时的行为。编译乱序可以通过编译屏障来解决,而执行乱序一般通过读内存屏障、写内存屏障以及读写内存屏障来解决。

一个专注于“嵌入式知识分享”、“DIY嵌入式产品”的技术开发人员,关注我,一起共创嵌入式联盟。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HUAWEI MPLS 静态配置和动态LDP配置

MPLS(Multi-Protocol Label Switching,多协议标签交换技术)技术的出现,极大地推动了互联网的发展和应用。例如:利用MPLS技术,可以有效而灵活地部署VPN(Virtual Private Network,虚拟专用网),TE(Traffic Eng…

将iStoreOS部署到VMware ESXi变成路由器

正文共:888 字 19 图,预估阅读时间:1 分钟 前面把iStoreOS部署到了VMware workstation上(将iStoreOS部署到VMware Workstation)。如果想把iStoreOS直接部署到ESXi上,你会发现转换镜像不能直接生成OVF或者OV…

巴比达内网穿透:重塑企业级数据通信的高效与安全边界

在当今数据驱动的时代,企业对于高效、安全、低延迟的数据传输需求日益迫切。巴比达,作为企业级内网穿透领域的佼佼者,凭借其自主研发的第九代核心引擎——WanGooe Tunnel,为企业带来了前所未有的通信体验。通过一系列技术创新与优…

亚马逊云科技AWS免费大热AI应用开发证书(含题库、开卷)

亚马逊云科技AWS官方生成式AI免费证书来了!内含免费AI基础课程!快速掌握AWS的前沿AI技术,后端开发程序员也可以速成AI专家,了解当下最🔥的AWS AI架构解决方案! 本证书内容包括AWS上的AI基础知识&#xff0c…

强化学习-5 策略梯度、Actor-Critic 算法

文章目录 1 基于价值( value-based \text{value-based} value-based )算法的缺点2 策略梯度算法2.1 解释2.1.1 分母和分子相消2.1.2 对数函数的导数2.1.3 组合公式2.1.4 总结 3 REINFORCE算法4 策略梯度推导进阶4.1 平稳分布4.2 基于平稳分布的策略梯度…

家用洗地机什么牌子好?四款公认品牌好的机型推荐

每个人都希望自己的家里面能够干干净净,就算不是一尘不染,也至少应该是整洁的,但是在这个快节奏的大环境下,做清洁对于人们来说,不是没时间,就是太累了。正当此时,一款造福懒人的神器——家用洗…

IT行业入门,如何假期逆袭,实现抢跑

目录 前言 1.IT行业领域分类 2.基础课程预习指南 3.技术学习路线 4.学习资源推荐 结束语 前言 IT(信息技术)行业是一个非常广泛和多样化的领域,它包括了许多不同的专业领域和职业路径。如果要进军IT行业,我们应该要明确自己…

python-数据容器对比总结

基于各类数据容器的特点,它们的应用场景如下: 数据容器的通用操作 - 遍历 数据容器的通用统计功能 容器的通用转换功能 容器通用排序功能 容器通用功能总览

GIT - 一条命令把项目更新到远程仓库

前言 阅读本文大概需要1分钟 说明 更新项目到远程仓库只需要执行一条命令,相当的简便 步骤 第一步 编辑配置文件 vim ~/.bash_profile第二步 写入配置文件 gsh() {local msg"${1:-ADD COMMIT PUSH}"git add . && git commit -m "$m…

市场布局企业增加 光场显示技术商业化进程将加快

市场布局企业增加 光场显示技术商业化进程将加快 光场显示技术是一种新型三维(3D)显示技术,是利用特殊显示和控光器件重构3D空间光场信息,实现3D动态显示。光场即光线在空间中的分布。   目前3D显示可分为真3D显示、助视3D显示、…

Kubernetes 离线安装的坑我采了

Kubernetes 离线安装的坑我采了 一、Error from server: Get "https://xx.xx.xx.xx:10250/containerLogs/kube-system/calico-node-8dnvs/calico-node": tls: failed to verify certificate: x509: certificate signed by unknown authority二、calico 或 pod 启动正…

【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(七)-shell语法(5)

shell语法的一些知识和练习,可以当作笔记收藏一下!! 文章目录 前言 一、shell 二、shell语法 1.文件重定向 2.引入外部脚本 3.作业 总结 前言 shell语法的一些知识和练习,可以当作笔记收藏一下!! 提示&…

CISSP是什么?值得考吗?

近期,国际信息系统安全认证联盟(ISC)宣布自2024年2月12日起,中国信息安全专业人员认证(CISSP)的中文考试将采用计算机自适应测试(CAT)形式进行。 计算机化自适应测试(CAT)根据考生答题情况动态调整后续试题的难度和类型,以更准确地衡量个人能…

2024最新ComfyUI文生图详解教程!

前言 leetcode,16.25. LRU 缓存 设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。 缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少…

网络基础:EIGRP

EIGRP(Enhanced Interior Gateway Routing Protocol)是由思科开发的一种高级距离矢量路由协议,结合了距离矢量和链路状态路由协议的优点;EIGRP具有快速收敛、高效带宽利用、负载均衡等特点,适用于各种规模的网络。EIGR…

基于YOLOv9+pyside的安检仪x光危险物物品检测(有ui)

安全检查在公共场所确保人身安全的关键环节,不可或缺。X光安检机作为必要工具,在此过程中发挥着重要作用。然而,其依赖人工监控和判断成像的特性限制了其应用效能。本文以此为出发点,探索了基于Torch框架的YOLO算法在安检X光图像中…

线性代数笔记

行列式 求高阶行列式 可以划上三角 上三角 余子式 范德蒙行列式 拉普拉斯公式 行列式行列对换值不变 矩阵 矩阵的运算 同型矩阵加减 对应位置相加减 矩阵的乘法 左边第 i 行 一次 相乘求和 右边 第 j 列 eg 中间相等 两边规模 矩阵的幂运算 解题思路 找规律 数学归纳…

智能体实战:开发一个集成国内AI平台的GPTs,自媒体高效智能助手

文章目录 一,什么是GPTs二,开发GPTs1,目标2,开发2.1 打开 GPTS:https://chat.openai.com/gpts2.2 点击 Create 创建一个自己的智能体 2.3 配置GPTs2.4 配置外挂工具2.4.1 配置Authentication-授权2.4.1.1 生成语聚AI的…

人工智能-NLP简单知识汇总01

人工智能-NLP简单知识汇总01 1.1自然语言处理的基本概念 自然语言处理难点: 语音歧义句子切分歧义词义歧义结构歧义代指歧义省略歧义语用歧义 总而言之:!!语言无处不歧义 1.2自然语言处理的基本范式 1.2.1基于规则的方法 通…

医院挂号系统:基于JSP和MySQL的现代化医疗预约平台

开头语:您好,我是专注于医疗系统开发的IT学长。如果您对医院挂号系统感兴趣,欢迎联系我。 开发语言:Java 数据库:MySQL 技术:JSP技术,B/S架构 工具:Eclipse,MyEclips…