【Linux】内存屏障

内存屏障的缘由

内存屏障的引入,本质上由于CPU重排指令引起的。

  1. 编译器编译时的优化;
  2. 处理器执行时的多发射和乱序优化;
  3. 读取存储指令的优化;
  4. 缓存同步顺序;

**内存一致性模型(Memory Consistency Model)**是用来描述多线程对共享存储器的访问行为,在不同的内存一致性模型里,多线程对共享存储器的访问行为有非常大的差别。这些差别会严重影响程序的执行逻辑,甚至会造成软件逻辑问题。

不同的处理器架构,使用了不同的内存一致性模型,目前有多种内存一致性模型,从上到下模型的限制由强到弱:

顺序一致性(Sequential Consistency)模型
完全存储定序(Total Store Order)模型
部分存储定序(Part Store Order)模型
宽松存储(Relax Memory Order)模型
注意,这里说的内存模型是针对可以同时执行多线程的平台,如果只能同时执行一个线程,也就是系统中一共只有一个CPU核,那么它一定是满足顺序一致性模型的。

对于内存的访问,我们只关心两种类型的指令的顺序,一种是读取,一种是写入。对于读取和加载指令来说,它们两两一起,一共有四种组合:

LoadLoad:前一条指令是读取,后一条指令也是读取。
LoadStore:前一条指令是读取,后一条指令是写入。
StoreLoad:前一条指令是写入,后一条指令是读取。
StoreStore:前一条指令是写入,后一条指令也是写入。

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

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

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

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

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

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

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

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

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

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

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

你可能感兴趣的:(Linux,linux,c++)