22
33直接上,不多 BB 了。
44
5- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/缓存一致性提纲.png )
5+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/缓存一致性提纲.png )
66
77---
88
1212
1313CPU Cache 通常分为三级缓存:L1 Cache、L2 Cache、L3 Cache,级别越低的离 CPU 核心越近,访问速度也快,但是存储容量相对就会越小。其中,在多核心的 CPU 里,每个核心都有各自的 L1/L2 Cache,而 L3 Cache 是所有核心共享使用的。
1414
15- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost2/操作系统/存储结构/CPU-Cache.png )
15+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost2/操作系统/存储结构/CPU-Cache.png )
1616
1717我们先简单了解下 CPU Cache 的结构,CPU Cache 是由很多个 Cache Line 组成的,CPU Line 是 CPU 从内存读取数据的基本单位,而 CPU Line 是由各种标志(Tag)+ 数据块(Data Block)组成,你可以在下图清晰的看到:
1818
19- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/Cache的数据结构.png )
19+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/Cache的数据结构.png )
2020
2121
2222
@@ -35,7 +35,7 @@ CPU Cache 通常分为三级缓存:L1 Cache、L2 Cache、L3 Cache,级别越
3535
3636保持内存与 Cache 一致性最简单的方式是,** 把数据同时写入内存和 Cache 中** ,这种方法称为** 写直达(* Write Through* )** 。
3737
38- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/写直达.png )
38+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/写直达.png )
3939
4040
4141在这个方法里,写入前会先判断数据是否已经在 CPU Cache 里面了:
@@ -52,22 +52,22 @@ CPU Cache 通常分为三级缓存:L1 Cache、L2 Cache、L3 Cache,级别越
5252
5353在写回机制中,** 当发生写操作时,新的数据仅仅被写入 Cache Block 里,只有当修改过的 Cache Block「被替换」时才需要写到内存中** ,减少了数据写回内存的频率,这样便可以提高系统的性能。
5454
55-
56-
57- ![ ] ( https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/写回1.png )
55+ ![ ] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/写回1.png )
5856
5957
6058那具体如何做到的呢?下面来详细说一下:
6159
6260- 如果当发生写操作时,数据已经在 CPU Cache 里的话,则把数据更新到 CPU Cache 里,同时标记 CPU Cache 里的这个 Cache Block 为脏(Dirty)的,这个脏的标记代表这个时候,我们 CPU Cache 里面的这个 Cache Block 的数据和内存是不一致的,这种情况是不用把数据写到内存里的;
63- - 如果当发生写操作时,数据所对应的 Cache Block 里存放的是「别的内存地址的数据」的话,就要检查这个 Cache Block 里的数据有没有被标记为脏的,如果是脏的话,我们就要把这个 Cache Block 里的数据写回到内存,然后再把当前要写入的数据,写入到这个 Cache Block 里,同时也把它标记为脏的;如果 Cache Block 里面的数据没有被标记为脏,则就直接将数据写入到这个 Cache Block 里,然后再把这个 Cache Block 标记为脏的就好了。
61+ - 如果当发生写操作时,数据所对应的 Cache Block 里存放的是「别的内存地址的数据」的话,就要检查这个 Cache Block 里的数据有没有被标记为脏的:
62+ - 如果是脏的话,我们就要把这个 Cache Block 里的数据写回到内存,然后再把当前要写入的数据,先从内存读入到 Cache Block 里(注意,这一步不是没用的,具体为什么要这一步,可以看这个「[ 回答] ( https://stackoverflow.com/questions/26672661/for-write-back-cache-policy-why-data-should-first-be-read-from-memory-before-w ) 」),然后再把当前要写入的数据写入到 Cache Block,最后也把它标记为脏的;
63+ - 如果 Cache Block 里面的数据没有被标记为脏,则就直接将数据写入到这个 Cache Block 里,然后再把这个 Cache Block 标记为脏的就好了。
6464
6565
6666可以发现写回这个方法,在把数据写入到 Cache 的时候,只有在缓存不命中,同时数据对应的 Cache 中的 Cache Block 为脏标记的情况下,才会将数据写到内存中,而在缓存命中的情况下,则在写入后 Cache 后,只需把该数据对应的 Cache Block 标记为脏即可,而不用写到内存里。
6767
6868这样的好处是,如果我们大量的操作都能够命中缓存,那么大部分时间里 CPU 都不需要读写内存,自然性能相比写直达会高很多。
6969
70-
70+ 为什么缓存没命中时,还要定位 cache block?这是因为此时是要判断数据即将写入到 cache block 里的位置,是否被「其他数据」占用了此位置,如果这个「其他数据」是脏数据,那么就要帮忙把它写回到内存。
7171
7272---
7373
@@ -79,40 +79,40 @@ CPU Cache 通常分为三级缓存:L1 Cache、L2 Cache、L3 Cache,级别越
7979
8080假设 A 号核心和 B 号核心同时运行两个线程,都操作共同的变量 i(初始值为 0 )。
8181
82- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/缓存一致性问题例子.png )
82+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/缓存一致性问题例子.png )
8383
8484这时如果 A 号核心执行了 ` i++ ` 语句的时候,为了考虑性能,使用了我们前面所说的写回策略,先把值为 ` 1 ` 的执行结果写入到 L1/L2 Cache 中,然后把 L1/L2 Cache 中对应的 Block 标记为脏的,这个时候数据其实没有被同步到内存中的,因为写回策略,只有在 A 号核心中的这个 Cache Block 要被替换的时候,数据才会写入到内存里。
8585
8686如果这时旁边的 B 号核心尝试从内存读取 i 变量的值,则读到的将会是错误的值,因为刚才 A 号核心更新 i 值还没写入到内存中,内存中的值还依然是 0。** 这个就是所谓的缓存一致性问题,A 号核心和 B 号核心的缓存,在这个时候是不一致,从而会导致执行结果的错误。**
8787
88- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/缓存一致性问题例子2.png )
88+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/缓存一致性问题例子2.png )
8989
9090
9191那么,要解决这一问题,就需要一种机制,来同步两个不同核心里面的缓存数据。要实现的这个机制的话,要保证做到下面这 2 点:
9292
93- - 第一点,某个 CPU 核心里的 Cache 数据更新时,必须要传播到其他核心的 Cache,这个称为** 写传播(* Wreite Propagation* )** ;
94- - 第二点,某个 CPU 核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的,这个称为** 事务的串形化 (* Transaction Serialization* )** 。
93+ - 第一点,某个 CPU 核心里的 Cache 数据更新时,必须要传播到其他核心的 Cache,这个称为** 写传播(* Write Propagation* )** ;
94+ - 第二点,某个 CPU 核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的,这个称为** 事务的串行化 (* Transaction Serialization* )** 。
9595
96- 第一点写传播很容易就理解,当某个核心在 Cache 更新了数据,就需要同步到其他核心的 Cache 里。而对于第二点事务的串形化 ,我们举个例子来理解它。
96+ 第一点写传播很容易就理解,当某个核心在 Cache 更新了数据,就需要同步到其他核心的 Cache 里。而对于第二点事务的串行化 ,我们举个例子来理解它。
9797
9898假设我们有一个含有 4 个核心的 CPU,这 4 个核心都操作共同的变量 i(初始值为 0 )。A 号核心先把 i 值变为 100,而此时同一时间,B 号核心先把 i 值变为 200,这里两个修改,都会「传播」到 C 和 D 号核心。
9999
100- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/事件顺序问题.png )
100+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/事件顺序问题.png )
101101
102102
103103那么问题就来了,C 号核心先收到了 A 号核心更新数据的事件,再收到 B 号核心更新数据的事件,因此 C 号核心看到的变量 i 是先变成 100,后变成 200。
104104
105105而如果 D 号核心收到的事件是反过来的,则 D 号核心看到的是变量 i 先变成 200,再变成 100,虽然是做到了写传播,但是各个 Cache 里面的数据还是不一致的。
106106
107107
108- 所以,我们要保证 C 号核心和 D 号核心都能看到** 相同顺序的数据变化** ,比如变量 i 都是先变成 100,再变成 200,这样的过程就是事务的串形化 。
108+ 所以,我们要保证 C 号核心和 D 号核心都能看到** 相同顺序的数据变化** ,比如变量 i 都是先变成 100,再变成 200,这样的过程就是事务的串行化 。
109109
110- 要实现事务串形化 ,要做到 2 点:
110+ 要实现事务串行化 ,要做到 2 点:
111111
112112- CPU 核心对于 Cache 中数据的操作,需要同步给其他 CPU 核心;
113113- 要引入「锁」的概念,如果两个 CPU 核心里有相同数据的 Cache,那么对于这个 Cache 数据的更新,只有拿到了「锁」,才能进行对应的数据更新。
114114
115- 那接下来我们看看,写传播和事务串形化具体是用什么技术实现的 。
115+ 那接下来我们看看,写传播和事务串行化具体是用什么技术实现的 。
116116
117117---
118118
@@ -124,9 +124,9 @@ CPU Cache 通常分为三级缓存:L1 Cache、L2 Cache、L3 Cache,级别越
124124
125125可以发现,总线嗅探方法很简单, CPU 需要每时每刻监听总线上的一切活动,但是不管别的核心的 Cache 是否缓存相同的数据,都需要发出一个广播事件,这无疑会加重总线的负载。
126126
127- 另外,总线嗅探只是保证了某个 CPU 核心的 Cache 更新数据这个事件能被其他 CPU 核心知道,但是并不能保证事务串形化 。
127+ 另外,总线嗅探只是保证了某个 CPU 核心的 Cache 更新数据这个事件能被其他 CPU 核心知道,但是并不能保证事务串行化 。
128128
129- 于是,有一个协议基于总线嗅探机制实现了事务串形化 ,也用状态机机制降低了总线带宽压力,这个协议就是 MESI 协议,这个协议就做到了 CPU 缓存一致性。
129+ 于是,有一个协议基于总线嗅探机制实现了事务串行化 ,也用状态机机制降低了总线带宽压力,这个协议就是 MESI 协议,这个协议就做到了 CPU 缓存一致性。
130130
131131---
132132
@@ -164,11 +164,11 @@ MESI 协议其实是 4 个状态单词的开头字母缩写,分别是:
164164
165165事实上,整个 MESI 的状态可以用一个有限状态机来表示它的状态流转。还有一点,对于不同状态触发的事件操作,可能是来自本地 CPU 核心发出的广播事件,也可以是来自其他 CPU 核心通过总线发出的广播事件。下图即是 MESI 协议的状态图:
166166
167- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/MESI协议.png )
167+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU缓存一致性/MESI协议.png )
168168
169169 MESI 协议的四种状态之间的流转过程,我汇总成了下面的表格,你可以更详细的看到每个状态转换的原因:
170170
171- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7/%20MESI%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E8%A1%A8%E6%A0%BC.png )
171+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/CPU%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7/%20MESI%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E8%A1%A8%E6%A0%BC.png )
172172
173173
174174---
@@ -187,24 +187,17 @@ CPU 在读写数据的时候,都是在 CPU Cache 读写数据的,原因是 C
187187要想实现缓存一致性,关键是要满足 2 点:
188188
189189- 第一点是写传播,也就是当某个 CPU 核心发生写入操作时,需要把该事件广播通知给其他核心;
190- - 第二点是事务的串行化 ,这个很重要,只有保证了这个,才能保障我们的数据是真正一致的,我们的程序在各个不同的核心上运行的结果也是一致的;
190+ - 第二点是事物的串行化 ,这个很重要,只有保证了这个,才能保障我们的数据是真正一致的,我们的程序在各个不同的核心上运行的结果也是一致的;
191191
192192基于总线嗅探机制的 MESI 协议,就满足上面了这两点,因此它是保障缓存一致性的协议。
193193
194- MESI 协议,是已修改、独占、共享、已实现这四个状态的英文缩写的组合 。整个 MSI 状态的变更,则是根据来自本地 CPU 核心的请求,或者来自其他 CPU 核心通过总线传输过来的请求,从而构成一个流动的状态机。另外,对于在「已修改」或者「独占」状态的 Cache Line,修改更新其数据不需要发送广播给其他 CPU 核心。
194+ MESI 协议,是已修改、独占、共享、已失效这四个状态的英文缩写的组合 。整个 MSI 状态的变更,则是根据来自本地 CPU 核心的请求,或者来自其他 CPU 核心通过总线传输过来的请求,从而构成一个流动的状态机。另外,对于在「已修改」或者「独占」状态的 Cache Line,修改更新其数据不需要发送广播给其他 CPU 核心。
195195
196196
197197---
198198
199199## 关注作者
200200
201+ ![ ] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost3@main/其他/公众号介绍.png )
201202
202- 前几个星期建的技术交流群,没想到很快就满 500 人了,群里的大牛真的多,大家交流都很踊跃,也有很多热心分享和回答问题的小伙伴。
203-
204- 不过没关系,小林最近又新建了技术交流群,相信这里是你交朋友好地方,也是你上班划水的好入口。
205-
206- 准备入冬了,一起来抱团取暖吧,** 群满 100、200、300、500 人,小林都会发红包的** ,赶快来吧,加群方式很简单,扫码下方二维码,回复「** 加群** 」。
207-
208- ![ ] ( https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost3@main/其他/公众号介绍.png )
209-
210- *** 哈喽,我是小林,就爱图解计算机基础,如果觉得文章对你有帮助,欢迎微信搜索「小林coding」,关注后,回复「网络」再送你图解网络 PDF***
203+ *** 哈喽,我是小林,就爱图解计算机基础,如果觉得文章对你有帮助,欢迎微信搜索「小林coding」,关注后,回复「网络」再送你图解网络 PDF***
0 commit comments