Skip to content

Commit 14195eb

Browse files
authored
更新cpu系列文章的md文件
1 parent 911d7c7 commit 14195eb

7 files changed

Lines changed: 182 additions & 187 deletions

File tree

‎os/1_hardware/cpu_mesi.md‎

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
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

@@ -12,11 +12,11 @@
1212

1313
CPU 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

Comments
 (0)