CPU实现原子操做的原理

586以前的CPU, 会经过LOCK锁总线的形式来实现原子操做. 686开始则提供了存储一致性(Cache coherence),  这是多处理的基础, 也是原子操做的基础.git

 

1. 存储的粒度编程

存储的组织形式(粒度)是以CacheLine为单位的, 一般为64字节甚至更高(早期也有32字节的). 而后几组CacheLine组成一个小的LRU(或者其余替换规则).缓存

 

2. 协议服务器

存储一致性(CC)通常是经过MESI协议, 以及后续的变种协议, 例如Intel的MESIF协议和AMD的MOESI协议, 来实现的. 多线程

以MESI协议为例:架构

Modified: 独占CacheLine, 已经修改了, 可是还未同步到主存优化

Exclusive: 独占, 而且和主存一致线程

Shared : 共享的, 其余core也拥有该CacheLine, 而且与主存中一致server

Invalid: 表示该CacheLine不可用对象

 

3. 通信

有了协议, 那么就须要通信来实现协议(存储的状态). 通信有两种, 一种是广播/侦听, 一种是目录式.

广播/侦听顾名思义就是存储状态的变动, 会被广播到其余core上面去, 进而去维护CacheLine的状态. 很明显这种方式会浪费大量的流量, 并且难以扩展, CPU核数多了, 总线就是明显的瓶颈.

目录式, 就是把改变通知给具体的core, 从而避免广播. 可是考虑一种极端状况, 若是不少个core都在访问同一个CacheLine, 那仍是不能避免(事实上的)广播. 因此, 多线程编程时, 共享同一个CacheLine不是一个好的选择.

 

 有了上面的东西, 如今咱们来考虑原子操做的实现:

 

1. 原子的Load/Store

因为CPU对缓存的管理是以CacheLine为单位的, 因此在一个CacheLine内load/store实际上都是原子的. Load和Store一个8字节对象, 不可能高4位和低4位是分开操做的(从而搞成俩值). 

可是光有这个实际上还不够, CPU对CacheLine的修改不是当即写到主存里面去, 因此其余Core看到的值就有多是老的值, 因此这时候还须要fence来读到最新的值; 至于写, 那必定须要写权限, 即M或者E状态, 而这两个权限里面都有最新的值(只是你刚才读到的不必定是最新的, 因此有可能用老值覆盖了新值).

2. FetchAndAdd

这是比load和store稍微复杂的操做, 其实是一个复合操做. 可是有了M和E状态, 就很好理解了:

lock(CacheLine)
v := load(obj)
v += add
store(obj, v)
release(CacheLine)

x86里面是xadd指令.

3. CompareAndSwap

那么CAS, 也就能够猜出来:

lock(CacheLine)
v := load(obj)
if v != expected {
  store(obj, new_value)
}
release(CacheLine)

x86里面是xchg

这里说的lock和release均表示对该CacheLine独占和解出独占的意思.

 

关于原子操做的原理, 鲜有资料表表示其具体怎么作的, 颇有多是过于偏向于硬件. 可是对MESI等协议的思考, 实际上仍是能猜到CPU内部的实现(至少七八不离十).  好在找到两个资料, 一个是<<并行多核体系结构基础>>和<<从鲲鹏920了解现代服务器实现和引用>>. 其中鲲鹏920内存模型章节这么写到:

原子指令在软件上看来逻辑并不复杂,但在微架构上看,成本是很高的。若是咱们把CPU 和内存都看作是总线上的一个个独立的实体,有一个CPU要作CAS指令,这个CPU须要先从 内存中读一个值,同时要在内存控制器上设置一个标志,保证其余CPU写不进去,等它比 较完了,而后再决定写一个值回去,才会让其余CPU写入。

不一样微架构实现有不一样方法对行为进行优化,在鲲鹏920上,原子指令的请求须要在 L3Cache上进行排队,保证在原子操做的多个动做之间能维持原子指令要求的语义。这个 排队自己也有成本。因此没有原子须要就不要轻易用原子变量,这实际上是有成本的。

 并行多核体系结构这么写到:

幸运的是, 缓存一致性协议提供了原子性被保障的基础. 举例来讲, 当遇到一个原子指令时, 这个协议知道须要保证原子性. 他首先得到对存储单元M的"独家全部权" (经过将其余包含M的缓存块中的拷贝都置为无效). 当得到独家全部权以后, 这个协议会确保只有一个处理器可以访问这个块, 而若是其余处理器在此时想要访问的话就会经历缓存缺失, 接下来原子指令就能够执行. 在原子指令持续期间, 其余处理器不容许"偷走"这个块. 距离来讲, 如通另外一个处理器要求读或者写这个块, 这个块就被"偷"了(如块被清理, 块的状态被降级为无效). 在原子指令完成以前暴露块会破坏指令的原子性, ......

参考:

1) 并行多核体系结构基础

2) 从鲲鹏920了解现代服务器实现和应用

相关文章
相关标签/搜索