CPU实现原子操作的原理
586之前的CPU, 会通过LOCK锁总线的形式来实现原子操作. 686开始则提供了存储一致性(Cache coherence), 这是多处理的基础, 也是原子操作的基础.
1. 存储的粒度
存储的组织形式(粒度)是以CacheLine为单位的, 通常为64字节甚至更高(早期也有32字节的). 然后几组CacheLine组成一个小的LRU(或者其他替换规则).
2. 协议
存储一致性(CC)一般是通过MESI协议, 以及后续的变种协议, 例如Intel的MESIF协议和AMD的MOESI协议, 来实现的.
以MESI协议为例:
Modified: 独占CacheLine, 已经修改了, 但是还未同步到主存
Exclusive: 独占, 并且和主存一致
Shared : 共享的, 其他core也拥有该CacheLine, 并且与主存中一致
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) 并行多核体系结构基础
CPU实现原子操作的原理的更多相关文章
- 代码中理解CPU结构及工作原理
一.前言 从研究生开始到工作半年,陆续在接触MCU SOC这些以CPU为核心的控制器,但由于专业的原因一直对CPU的内部结构和工作原理一知半解.今天从一篇博客中打破一直以来的盲区.特此声明,本文设计思 ...
- 1、cpu架构和工作原理
cpu架构和工作原理 计算机有5大基本组成部分,运算器,控制器,存储器,输入和输出.运算器和控制器封装到一起,加上寄存器组和cpu内部总线构成中央处理器(CPU).cpu的根本任务,就是执行指令,对计 ...
- CPU GPU设计工作原理《转》
我知道这非常长,可是,我坚持看完了.希望有幸看到这文章并对图形方面有兴趣的朋友,也能坚持看完.一定大有收获.毕竟知道它们究竟是怎么"私下勾搭"的.会有利于我们用程序来指挥它们... ...
- CPU内部组成及原理
CPU,Central Processing Unit,翻译过来叫中央处理器.是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Unit).电脑中所有操作都由C ...
- CPU中断的工作原理,从最底层讲起
前言 中断的概念属于硬件层.虽然我们在进行软件编程时不会直接使用中断,但理解它对我们来说依然重要. 我们在使用线程切换及状态管理.异常处理.硬件与处理器的交互.I/O操作等指令时,中断都在默默的为我们 ...
- CPU 多核指令 —— WFE 原理【原创】
转自:http://tinylab.org/arm-wfe/ Zhang Binghua 创作于 2020/05/19 打赏 微信公众号 知识星球 关注 @泰晓科技 与数千位一线 Linux 工程 ...
- CPU性能分析工具原理
转载请保留以下声明 作者:赵宗晟 出处:https://www.cnblogs.com/zhao-zongsheng/p/13067733.html 很多软件都要做性能分析和性能优化.很多语言都会有他 ...
- CPU 和内存虚拟化原理 - 每天5分钟玩转 OpenStack(6)
前面我们成功地把 KVM 跑起来了,有了些感性认识,这个对于初学者非常重要.不过还不够,我们多少得了解一些 KVM 的实现机制,这对以后的工作会有帮助. CPU 虚拟化 KVM 的虚拟化是需要 CPU ...
- cpu卡,sam卡原理
第一部分 CPU基础知识一.为什么用CPU卡IC卡从接口方式上分,可以分为接触式IC卡.非接触式IC卡及复合卡.从器件技术上分,可分为非加密存储卡.加密存储卡及CPU卡.非加密卡没有安全性,可以任意改 ...
随机推荐
- nextInt()和nextLine()连用报错
当nextInt(),next(),nextDouble(),nextFloat()方法与nextLine()连用并放在nextLine()前面时,会出现下面的错误: Exception in thr ...
- Windows Server 2012 R2 辅助域控制器搭建
Windows Server 2012 R2 辅助域控制器搭建 以下操作都是基于主域已搭建成功的基础上,全程操作过程都是在辅域上操作完成. 地址 主域:10.228.81.207 辅域:10.228. ...
- 833. Find And Replace in String —— weekly contest 84
Find And Replace in String To some string S, we will perform some replacement operations that replac ...
- .NetCore操作MongDB简要代码实现
.NetCore操作MongoDB简要代码实现 在接触过的大多数使用mongodb的情景中,基本上都是用mongodb来存储日志的. mongodb是作为一种文档型的数据库,在管理日志文档上确实比较适 ...
- 限制页面只能由微信内置浏览器打开,在其他浏览器打开跳转到Oauth2页面
在需要限制的页面加上 appid必填,可以获取也可以自己随意 <script> var ua = navigator.userAgent.toLowerCase(); var isWei ...
- Boltzmann Machine 玻尔兹曼机入门
Generative Models 生成模型帮助我们生成新的item,而不只是存储和提取之前的item.Boltzmann Machine就是Generative Models的一种. Boltzma ...
- python类继承中构造子的调用
python面向对象中的继承关系中,子类对父类的构造方法的调用有两种方法: 父类名.__init__(self,参数) #注意名字是父类 super(本子类名,self)__init__(其他参数) ...
- lua调用dll demo
使用的是lua5.3 DllMain.cpp 1 //生成的dll 是 lua_add53.dll 2 //luaopen_lua_add 3 extern "C" { 4 #in ...
- 三、分布式编程总结------linux多线程服务端编程
- NO.A.0001——day01——Java概述/进制间的转换
一.什么是JAVA语言: JAVA语言是美国sun公司(Stanford University Network)在1995年推出的高级编程语言.所谓编程语言,是计算机的语言,人们可以使用 ...