微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 硬件工程师文库 > 从硬件引申出内存屏障,带你深入了解Linux内核RCU

从硬件引申出内存屏障,带你深入了解Linux内核RCU

时间:08-19 来源:电子发烧友网工程师 点击:

本文简介

本文从硬件的角度引申出内存屏障,这不是内存屏障的详尽手册,但是相关知识对于理解RCU有所帮助。这不是一篇单独的文章,这是《谢宝友:深入理解Linux RCU》系列的第2篇,前序文章:

谢宝友: 深入理解Linux RCU之一——从硬件说起

作者简介

         谢宝友,在编程一线工作已经有20年时间,其中接近10年时间工作于Linux操作系统。在中兴通讯操作系统产品部工作期间,他作为技术总工参与的电信级嵌入式实时操作系统,获得了行业最高奖----中国工业大奖。

同时,他也是《深入理解并行编程》一书的译者。该书作者Paul E.McKeney是IBM Linux中心领导者,Linux RCU Maintainer。《深入理解RCU》系列文章整理了Paul E.McKeney的相关著作,希望能帮助读者更深刻的理解Linux内核中非常难于理解的模块----RCU。

联系方式:

mail:scxby@163.com

微信:linux-kernel

稿件征集

欢迎您给Linuxer投稿,赢得人民邮电异步社区任意在售技术图书。您随便挑,详情:Linuxer-"Linux开发者自己的媒体"首月稿件录取和赠书名单

Linuxer-"Linux开发者自己的媒体"第二月稿件录取和赠书名单

一、内存Cache还有哪些不足?

上一篇文章我们谈到了内存Cache,并且描述了典型的Cache一致性协议MESI。Cache的根本目的,是解决内存与CPU速度多达两个数量级的性能差异。一个包含Cache的计算机系统,其结构可以简单的表示为下图:

仅仅只有Cache的计算机系统,它还存在如下问题:

1、Cache的速度,虽然比内存有了极大的提升,但是仍然比CPU慢几倍。

2、在发生"warmup cache miss"、"capacity miss"、"associativity miss"时,CPU必须等待从内存中读取数据,此时CPU会处于一种Stall的状态。其等待时间可能达到几百个CPU指令周期。

显然,这是现代计算机不能承受之重:)

二、Write buffer是为了解决什么问题?

如果CPU仅仅是执行foo = 1这样的语句,它其实无须从内存或者缓存中读取foo现在的值。因为无论foo当前的值是什么,它都会被覆盖。在仅仅只有Cache的系统中,foo = 1 这样的操作也会形成写停顿。自然而然的,CPU设计者应当会想到在Cache 和CPU之间再添加一级缓存。由于这样的缓存主要是应对写操作引起的Cache Miss,并且缓存的数据与写操作相关,因此CPU设计者将它命名为"Write buffer"。调整后的结构示意图如下(图中的store buffer即为write buffer):

通过增加这些Write buffer,CPU可以简单的将要保存的数据放到Write buffer 中,并且继续运行,而不会真正去等待Cache从内存中读取数据并返回。

对于特定CPU来说,这些Write buffer是属于本地的。或者在硬件多线程系统中,它对于特定核来说,是属于本地的。无论哪一种情况,一个特定CPU仅仅允许访问分配给它的Writebuffer。例如,在上图中,CPU 0不能访问CPU 1的存储缓冲,反之亦然。

Write buffer进一步提升了系统性能,但是它也会为硬件设计者带来一些困扰:

第一个困扰:违反了自身一致性。

考虑如下代码:变量"a"和"b"都初始化为0,包含变量"a"缓存行,最初被CPU 1所拥有,而包含变量"b"的缓存行最初被CPU0所拥有:

  1   a = 1;

  2   b = a + 1;

  3   assert(b == 2);

没有哪一位软件工程师希望断言被触发!

然而,如果采用上图中的简单系统结构,断言确实会被触发。理解这一点的关键在于:a最初被CPU 1所拥有,而CPU 0在执行a = 1时,将a的新值存储在CPU 0的Write buffer中。

在这个简单系统中,触发断言的事件顺序可能如下:

1.CPU 0 开始执行a = 1。

2.CPU 0在缓存中查找"a",并且发现缓存缺失。

3.因此,CPU 0发送一个"读使无效(read-invalidate message)"消息,以获得包含"a"的独享缓存行。

4.CPU 0将"a"记录到存储缓冲区。

5.CPU 1接收到"读使无效"消息,它通过发送缓存行数据,并从它的缓存行中移除数据来响应这个消息。

6.CPU 0开始执行b = a + 1。

7.CPU 0从CPU 1接收到缓存行,它仍然拥有一个为"0"的"a"值。

8.CPU 0从它的缓存中读取到"a"的值,发现其值为0。

9.CPU 0将存储队列中的条目应用到最近到达的缓存行,设置缓存行中的"a"的值为1。

10.CPU 0将前面加载的"a"值0加1,并存储该值到包含"b"的缓存行中(假设已经被CPU 0所拥有)。

11.CPU 0 执行assert(b == 2),并引起错误。

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top