linux 内核同步互斥技术之处理器内存屏障

处理器内存屏障用来解决处理器之间的内存访问乱序问题和处理器访问外围设备的乱序问题。

现代CPU的运算速度比现代内存系统的速度快得多,它们的速度差了几个数量级,那怎么办呢?硬件设计者想到了在内存和CPU之间加入一个速度足够快,但空间不是很大的存储空间,这个就是所谓的缓存。缓存的速度足够快,但是它一般是某个或某些CPU核独享的,而不像计算机的主存,一般认为是系统中所有CPU共享的。

一旦引入了缓存,就会引入多个地方存放同一个数据的问题,就有可能出现数据不一致的问题。假设变量X所在内存同时被两个CPU都缓存了,但是这时候CPU0对变量X的值做出了修改,这之后CPU1如果试图读取变量X的值时,其实读到的是老的值。

这个时候就需要所谓的缓存一致性协议了,一般常用的是MESI协议。MESI代表“Modified”、“Exclusive”、“Shared”和“Invalid”四种状态的缩写,特定缓存行可以处在该协议采用的这四种状态上:

  1. 处于“Modified”状态的缓存行:当前CPU已经对缓存行的数据进行了修改,但是该缓存行的内容并没有在其它CPU的缓存中出现。因此,处于该状态的缓存行可以认为被当前CPU所“拥有”。这就是所谓的“脏”行,它的内容和内存中的内容不一样。由于只有当前CPU的缓存持有最新的数据,因此要么将“脏”数据写回到内存,要么将该数据“转移”给其它缓存。
  2. 处于“Exclusive”状态的缓存行:该状态非常类似于“Modified”状态,缓存的内容确保没有在其它CPU的缓存中出现。唯一的差别是,该缓存行还没有被当前的CPU修改,也就是说缓存行内容和内存中的是一样,是对内存数据的最新复制。但是,由于当前CPU能够在任何时刻将数据存储到该缓存行而不考虑其它CPU,因此处于“Exclusive”状态的缓存行也可以认为被当前CPU所“拥有”。
  3. 处于“Shared”状态的缓存行:表示缓存行的数据和主存中的一样,并且可能已经被复制到至少一个其它CPU的缓存中。但是,在没有得到其他CPU“许可”的情况下,任何CPU不能向该缓存行存储数据。与“Exclusive”状态相同,由于内存中的值是最新的,因此当需要丢弃该缓存行时,可以不用向内存回写。
  4. 处于“Invalid”状态的缓存行:表示该缓存行已经失效了,不能再被继续使用了。当有新数据进入缓存时,它可以直接放置到一个处于“Invalid”状态的缓存行上,不需要做其它的任何处理。

对于给定的两个缓存,以下是允许共同存在的状态:

M

E

S

I

M

E

S

I

EMSI状态转移图:

local read和local write分别代表本地CPU读写。remote read和remote write分别代表其他CPU读写。MESI协议在总线上的操作分成本地读写和总线操作。

上面这些操作,就是MESI协议规定的操作。初始状态下,当cache line中没有加载任何数据时,状态为I。本地读写指的是本地CPU读写自己私有的cache line,这是一个私有操作。总线读写指的是有总线的事务(bus transaction),因为实现的是总线监听协议,所以CPU可以发送请求到总线上,所有的CPU都可以收到这个请求。总之,总线读写的目标对象是远端CPU的高速缓存行,而本地读写的目标对象是本地CPU的高速缓存行。这些操作对我们理解 MESI状态的转换非常重要。

   当前状态

            事件

                                                    行为

下一个状态

I(invalid)

   local  read

1.如果其他处理器中没有这份数据,本缓存从内存中取该数据,状态变为E

2.如果其他处理器中有这份数据,且缓存行状态为M,则先把缓存行中的内容写回到内存。本地cache再从内存读取数据,这时两个cache的状态都变为S

3.如果其他缓存行中有这份数据,并且其他缓存行的状态为S或E,则本地cache从内存中取数据,并且这些缓存行的状态变为S

    E或S

  local  write

1.先从内存中取数据,如果其他缓存中有这份数据,且状态为M,则先将数据更新到内存再读取(个人认为顺序是这样的,其他CPU的缓存内容更新到内存中并且被本地cache读取时,两个cache状态都变为S,然后再写时把其他CPU的状态变为I,自己的变为M)

2.如果其他缓存中有这份数据,且状态为E或S,那么其他缓存行的状态变为I

     M 

 remote  read

remote read不影响本地cache的状态

     I

 remote  write

remote read不影响本地cache的状态

     I

E(exclusive)

 local  read

状态不变

     E

 local  write

状态变为M

     M

remote  read

数据和其他核共享,状态变为S

     S

remote  write

其他CPU修改了数据,状态变为I

     I

S(shared)

 local  read

不影响状态

     S

 local  write

其他CPU的cache状态变为I,本地cache状态变为M

     M

remote  read

不影响状态

     S

remote  write

本地cache状态变为I,修改内容的CPU的cache状态变为M

     I

M(modified)

 local  read

状态不变

     M

 local  write

状态不变

     M

remote  read

先把cache中的数据写到内存中,其他CPU的cache再读取,状态都变为S

     S

remote  write

先把cache中的数据写到内存中,其他CPU的cache再读取并修改后,本地cache状态变为I。修改的那个cache状态变为M

     I

为了维护这个状态机,需要各个CPU之间进行通信,会引入下面几种类型的消息:

  1. 读消息:该消息包含要读取的缓存行的物理地址。
  2. 读响应消息:该消息包含较早前的读消息所请求的数据,这个读响应消息要么由物理内存提供,要么由某一个其它CPU上的缓存提供。例如,如果某一个CPU上的缓存拥有处于“Modified”状态的目标数据,那么该CPU上的缓存必须提供读响应消息。
  3. 使无效消息:该消息包含要使无效的缓存行的物理地址,所有其它CPU上的缓存必须移除相应的数据并且响应此消息。
  4. 使无效应答消息:一个接收到使无效消息的CPU必须在移除指定数据后响应一个使无效应答消息。
  5. 读使无效消息:该消息包含要被读取的缓存行的物理地址,同时指示其它CPU上的缓存移除对应的数据。因此,正如名字所示,它将读消息和使无效消息合并成了一条消息。读使无效消息同时需要一个读响应消息及一组使无效应答消息进行应答。
  6. 写回消息:该包含要回写到物理内存的地址和数据。这个消息允许缓存在必要时换出处于“Modified”状态的数据,以便为其它数据腾出空间。

通过上面的介绍可以看到,MESI缓存一致性协议可以保证系统中的各个CPU核上的缓存都是一致的。但是也带来了一个很大的问题,由于所有的操作都是“同步”的,必须要等待远端CPU完成指定操作后收到响应消息才能真正执行对应的存储或加载操作,这样会极大降低系统的性能。比如说,如果CPU0和CPU1上同时缓存了同一段数据,如果CPU0想对其进行更改,那么必须先发送使无效消息给CPU1,等到CPU1真的将该缓存的数据段标记成“Invalid”状态后,会向CPU0发送使无效应答消息,理论上只有CPU0收到这个消息后,才可以真的更改数据。但是,从要更改到真的能更改已经经过了好几个阶段了,这时CPU0只能等在那里。

鱼和熊掌都兼得是不可能的,想提高性能,只能稍微放松一下对缓存一致性的要求。具体的,会引入如下两个模块:

  1. 存储缓冲:cpu写数据之前先要得到缓存段的独占权,如果当前CPU没有独占权,要先让系统中别的CPU上缓存的同一段数据都变成无效状态。为了提高性能,可以引入一个叫做存储缓冲(Store Buffer)的模块,将其放置在每个CPU和它的缓存之间。当前CPU发起写操作,如果发现没有独占权,可以先将要写入的数据放在存储缓冲中,并继续运行,仿佛独占权瞬间就得到了一样。当然,存储缓冲中的数据最后还是会被同步到缓存中的,但就相当于是异步执行了,不会让CPU等了。并且,当前CPU在读取数据的时候应该首先检查其是否存在于存储缓冲中。
  2. 无效队列:如果当前CPU上收到一条消息,要使某个缓存段失效,但是此时缓存正在处理其它事情,那这个消息可能无法在当前的指令周期中得到处理,而会将其放入所谓的无效队列(Invalidation Queue)中,同时立即发送使无效应答消息。那个待处理的使无效消息将保存在队列中,直到缓存有空为止。

处理器的硬件工程师使用存储缓冲区和使无效队列协助缓存和缓存一致性协议实现高性能,引入了处理器之间的内存访问乱序问题。

(1)写操作乱序问题,或者叫存储乱序问题。

假设执行顺序如下。

    1. 处理器 0 写的变量 a 不在本地缓存中,发送“读并且使无效”消息,然后把变量a 的新值写到存储缓冲区中,接着继续执行指令。
    2. 处理器 0 写的变量 b 在本地缓存中,假设处理器 1 的缓存中不包含变量 b。
    3. 处理器 1 读变量 b,因为变量 b 的新值在处理器 0 的缓存中,处理器 1 可以看到变量 b 的新值。
    4. 处理器 1 读变量 a,因为变量 a 的新值在处理器 0 的存储缓冲区中,处理器 1 看不到变量 a 的新值,要等处理器 0 把存储缓冲区中的数据冲刷到缓存之后才能看到变量 a 的新值。

处理器 0 首先写变量 a,然后写变量 b,可是处理器 1 看到变量 b 的新值时没有看到变量 a 的新值,看到的处理器 0 写的顺序好像是首先写变量 b,然后写变量 a。处理器 0 的存储缓冲区导致出现写操作乱序问题。

(2)读操作乱序问题,或者叫加载乱序问题。

假设处理器 0 和处理器 1 的缓存中都有变量 a,执行顺序如下。

    1. 处理器 0 写变量 a,发送使无效消息。
    2. 处理器 1 把使无效消息存放到使无效队列中,立即发送使无效确认消息,没有执行使包含变量 a 的缓存行无效的操作。
    3. 处理器 0 写的变量 b 在本地缓存中,假设处理器 1 的本地缓存中没有变量 b。
    4. 处理器 1 读变量 b,发送读消息,收到处理器 0 的读响应消息,读到变量 b 的新值。
    5. 处理器 1 读变量 a,从缓存中读到变量 a 的旧值。

处理器 0 首先写变量 a,然后写变量 b,可是处理器 1 看到变量 b 的新值时没有看到变量 a 的新值,处理器 1 的使无效队列导致出现读操作乱序问题。

外围设备控制器的寄存器和物理内存使用统一的物理地址空间,把外围设备控制器的寄存器的物理地址映射到内核的虚拟地址空间,像访问内存一样访问外围设备控制器的寄存器,称为内存映射 I/O。访问外围设备控制器的寄存器时,顺序很重要。例如,假设一个以太网卡有多个内部寄存器,如果想要读取一个内存寄部器的值,首先往地址端口寄存器写入内部寄存器的索引,然后从数据端口寄存器读取值。假设地址端口寄存器映射到虚拟地址 A,数据端口寄存器映射到虚拟地址 D,读取内部寄存器 5 的值,其代码如下:

*A = 5;

x = *D;

编译器和处理器不能识别出这种依赖关系,编译器可能重新排列这两行代码的顺序。采用超标量体系结构和乱序执行技术的处理器,可能不会按照程序顺序执行这两行代码。

内核有 8 种基本的处理器内存屏障,如下表所示:

内存屏障类型

强制性的内存屏障

SMP 内存屏障

通用内存屏障

mb()

smp_mb()

写内存屏障

wmb()

smp_wmb()

读内存屏障

rmb()

smp_rmb()

数据依赖屏障

read_barrier_depends()

smp_read_barrier_depends()

除了数据依赖屏障以外,所有的处理器内存屏障隐含编译器优化屏障。

SMP 内存屏障只在 SMP 系统中生效,解决处理器之间的内存访问乱序问题,在单处理器系统中退化为编译器优化屏障。

强制性的内存屏障在单处理器系统和 SMP 系统中都生效,在 SMP 系统中用来解决处理器之间的内存访问乱序问题和处理器访问外围设备的乱序问题,在单处理器系统中用来解决处理器访问外围设备的乱序问题。

写内存屏障解决写操作乱序问题,保证屏障前面的写操作看起来在屏障后面的写操作之前发生,也就是屏障前面的写操作必须在屏障后面的写操作之前被观察到,处理器之间的写操作乱序问题是由存储缓冲区引入的。

读内存屏障解决读操作乱序问题,保证屏障前面的读操作看起来在屏障后面的读操作之前发生,也就是屏障前面的读操作必须在屏障后面的读操作之前被观察到,处理器之间的读操作乱序问题是由使无效队列引入的。

通用内存屏障是写内存屏障和读内存屏障的组合,保证屏障前面的读和写操作看起来在屏障后面的读和写操作之前发生,也就是屏障前面的读和写操作必须在屏障后面的读和写操作之前被观察到。

解决处理器之间的内存访问乱序问题时,内存屏障必须配对使用:写者执行写内存屏障或通用内存屏障,读者执行读内存屏障或通用内存屏障,如下。

为什么内存屏障必须配对使用?因为处理器 1 读变量 a 的时候, 两种情况都可能出现。

(1)变量 a 的最新值在处理器 0 的存储缓冲区中,处理器 0 需要执行写内存屏障。

(2)处理器 1 的使无效队列包含使包含变量 a 的缓存行无效的消息,处理器 1 需要执行读内存屏障。

数据依赖屏障是更弱的读内存屏障,使用场合是第二个读操作依赖第一个读操作的结果,比如第一个读操作读指针的值,第二个读操作读指针指向的变量的值。

数据依赖屏障只在阿尔法(Alpha)处理器上生效,在其他处理器上是空操作。内核定义数据依赖屏障, 不直接使用读内存屏障, 目的是避免在除了阿尔法以外的处理器上产生额外的开销。

为什么阿尔法处理器需要数据依赖屏障?因为阿尔法处理器使用分区缓存,可以并行访问缓存的不同分区。假设下面的程序:

假设变量 B 的缓存行由缓存分区 0 处理,指针 P 的缓存行由缓存分区 1 处理。处理器1 执行“ B = 4”的时候,发送使无效消息,处理器 2 把使无效消息存放到使无效队列中,立即发送使无效确认消息。如果处理器 2 的缓存分区 0 很忙,缓存分区 1 空闲,缓存分区0 没有处理针对变量 B 的使无效消息,那么处理器 2 可能看见指针 P 的新值和变量 B 的旧值: P 的值是 B 的地址, B 的值是 2。

使用数据依赖屏障可以解决问题,其代码如下:

处理器 2 的缓存分区 0 执行数据依赖屏障,处理使无效队列中的消息,然后执行“ D =*Q”,如果看到指针 P 的值是变量 B 的地址,那么一定看到变量 B 的新值 4。

除了基本的内存屏障,内核还提供了以下高级的屏障函数。

(1) smp_store_mb(var, value)

给变量赋值,然后执行通用内存屏障。

(2) smp_mb__before_atomic()

放在原子操作函数的前面,执行通用内存屏障。例如:

*A = 5;

x = *D;obj->dead = 1;

smp_mb__before_atomic();

atomic_dec(&obj->ref_count);

(3) smp_mb__after_atomic()

放在原子操作函数的后面,执行通用内存屏障。

(4) lockless_dereference(p)

读取指针的值,里面封装了数据依赖屏障“ smp_read_barrier_depends()”。

(5) dma_wmb()和 dma_rmb()

保证访问处理器和支持 DMA 能力的设备共享的内存时写或读有序。

例如,假设设备驱动和设备共享内存,使用一个描述符状态值指示描述符属于设备或处理器,使用一个门铃在新的描述符可用时通知设备:

if (desc->status != DEVICE_OWN) {
/* 拥有描述符后才读数据 */
dma_rmb();
/* 读/修改数据 */
read_data = desc->data;
desc->data = write_data;
/* 在更新状态之前冲刷修改 */
dma_wmb();
/* 分配所有权 */
desc->status = DEVICE_OWN;
/* 在通过内存映射I/O通知设备之前强制同步内存 */
wmb();
/* 把新的描述符通告给设备 */
writel(DESC_NOTIFY, doorbell);
}

dma_rmb()保证处理器从描述符读数据之前设备释放了所有权。 dma_wmb()保证在设备看到它得到所有权之前把数据写到描述符。 wmb()保证在写到缓存不一致的内存映射 I/O 区域之前已经完成缓存一致的内存写操作。
 

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

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

相关文章

Python IDE Pycharm服务器配置方法并结合内网穿透工具实现远程开发

文章目录 一、前期准备1. 检查IDE版本是否支持2. 服务器需要开通SSH服务 二、Pycharm本地链接服务器测试1. 配置服务器python解释器 三、使用内网穿透实现异地链接服务器开发1. 服务器安装Cpolar2. 创建远程连接公网地址 四、使用固定TCP地址远程开发 本文主要介绍如何使用Pych…

【音视频 | H.264】H.264视频编码及NALU详解

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 🤣本文内容🤣&a…

spark介绍及简单使用

简介 Spark是由加州大学伯克利分校AMPLab(AMP实验室)开发的开源大数据处理框架。起初,Hadoop MapReduce是大数据处理的主流框架,但其存在一些限制,如不适合迭代算法、高延迟等。为了解决这些问题,Spark在20…

【数据结构】复习题(一)

一、选择题 1.组成数据的基本单位是()。 A. 数据项 B.数据类型 C.数据元素 D.数据变量 2.设数据结构A{D,R},其中D&#xff5b;1,2,3,4},R{r},r{<1,2>,<2,3>,< 3,4>,<4,1>}&#xff0c;则数据结构A是()。 A.线性结构 B.树型结构 C.图型结构 D.集合 3.…

鸿蒙HarmonyOS开发用什么语言

1.网上流行一句有中国底蕴的话&#xff1a;鸿蒙系统方舟框架盘古大模型。都方舟框架了肯定主推的是ArkUI框架。其实还能使用C、Java和Js开发。 2.从API8开始&#xff0c;Java语言已经从鸿蒙开发剔除了&#xff0c;而官方推荐的是ArkTs.下图是ArkTS与TS、JS的关系。 ArkTs 是TS的…

Programming Abstractions in C阅读笔记:p235-p241

《Programming Abstractions in C》学习第66天&#xff0c;p235-p241总结。 一、技术总结 1.backtracking algorithm(回溯算法) (1)定义 p236, For many real-world problem, the solution process consits of working your way through a sequence of decision points in…

统信UOS|DNS server|02-部署DNS服务器

原文链接&#xff1a;统信UOS&#xff5c;DNS server&#xff5c;02-部署DNS服务器 hello&#xff0c;大家好啊&#xff01;继上次我们介绍了如何在统信UOS操作系统1060上搭建一个测试用的HTTP服务器之后&#xff0c;今天我们将继续我们的DNS服务器部署系列。这是第二篇文章&am…

Ubuntu18.04 上通过 jihu 镜像完成 ESP-IDF 编译环境搭建流程

为了解决国内开发者从 github 克隆 esp 相关仓库慢的问题&#xff0c;已将 esp-idf 和部分重要仓库及其关联的子模块镜像到了 jihu&#xff0c;这些仓库将自动从原始仓库进行同步。此篇博客用来阐述 Ubuntu18.04 上通过 jihu 镜像完成 ESP-IDF 编译环境搭建流程。 注&#xff1…

IDEA shorten command line介绍和JAR manifest 导致mybatis找不到接口类处理

如果类路径太长&#xff0c;或者有许多VM参数&#xff0c;程序就无法启动。原因是大多数操作系统都有命令行长度限制。在这种情况下&#xff0c;IntelliJIDEA将试图缩短类路径。最好选中 classpath file模式。 shorten command line 选项提供三种选项缩短类路径。 none&#x…

HCIP —— BGP 基础实验

实验拓扑&#xff1a; 实验要求&#xff1a; 1.所有设备上均有环回接口 2.R1属于AS 100 &#xff0c;R2-R4 属于AS 200 &#xff0c;R5 属于AS 300 3.R2 - R4 属于同一个area &#xff0c;运行OSPF。 4.全网通过运行BGP实现网络互通。 实验步骤&#xff1a; 1.配置 IP地址…

时序预测 | Python实现LSTM-Attention电力需求预测

时序预测 | Python实现LSTM-Attention电力需求预测 目录 时序预测 | Python实现LSTM-Attention电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从而可以将预期预测与当前最先进的行…

UCloud + 宝塔 + PHP = 个人网站

UCloud 宝塔 PHP 个人网站 文章目录 1.概要2.UCloud使用教程&#xff08;租用云端服务器&#xff09;3.宝塔使用教程&#xff08;免费服务器运维面板&#xff09;4.总结 1.概要 今天主要是想教大家如何将在网络上白嫖到源码&#xff08;特指PHP源码!!!&#xff09;搭建运行…

大创项目推荐 深度学习 opencv python 公式识别(图像识别 机器视觉)

文章目录 0 前言1 课题说明2 效果展示3 具体实现4 关键代码实现5 算法综合效果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的数学公式识别算法实现 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学…

嵌入式中串口输入

学习目标 掌握串口初始化流程掌握串口接收逻辑了解中断接收逻辑熟练掌握串口开发流程学习内容 需求 串口接收PC机发送的数据。 串口数据接收 串口初始化 static void USART_config() {uint32_t usartx_tx_rcu = RCU_GPIOA;uint32_t usartx_tx_port = GPIOA;uint32_t usartx…

RabbitMQ入门指南(一):初识与安装

专栏导航 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、消息队列介绍 1.同步调用和异步调用 2.常见消息队列介绍 二、RabbitMQ简介及其安装步骤 1.RabbitMQ简介 2.RabbitMQ安装步骤&#xff08;使用Docker&#xff09; (1) 创建网络 (2) 使用Docker来…

Apache RocketMQ 5.0 腾讯云落地实践

Apache RocketMQ 发展历程回顾 RocketMQ 最早诞生于淘宝的在线电商交易场景&#xff0c;经过了历年双十一大促流量洪峰的打磨&#xff0c;2016年捐献给 Apache 社区&#xff0c;成为 Apache 社区的顶级项目&#xff0c;并在国内外电商&#xff0c;金融&#xff0c;互联网等各行…

【每日OJ—有效的括号(栈)】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 1、有效的括号题目&#xff1a; 1.1方法讲解&#xff1a; 1.2代码实现&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升起的太阳&#…

本地运行大语言模型并可视化(Ollama+big-AGI方案)

目前有两种方案支持本地部署&#xff0c;两种方案都是基于llamacpp。其中 Ollama 目前只支持 Mac&#xff0c;LM Studio目前支持 Mac 和 Windows。 LM Studio&#xff1a;https://lmstudio.ai/ Ollama&#xff1a;https://ollama.ai/download 本文以 Ollama 为例 step1 首先下…

九牧:科技卫浴,长期主义

“没有做错什么&#xff0c;但却输给了时代”&#xff0c;这是人们给当年手机巨头诺基亚的注解。 谁也没有想到&#xff0c;曾在手机行业称雄的诺基亚&#xff0c;最终败给了时代。当年&#xff0c;在2G向3G、4G跨越的时候&#xff0c;苹果、微软的iOS和安卓系统将手机从简单的…

MIT18.06线性代数 笔记1

文章目录 方程组的几何解释矩阵消元乘法和逆矩阵A的LU分解转置-置换-向量空间R列空间和零空间求解Ax0主变量 特解求解Axb可解性和解的结构线性相关性、基、维数四个基本子空间矩阵空间、秩1矩阵和小世界图图和网络复习一 方程组的几何解释 线性组合&#xff1a; 找到合适的x和…