CPU是如何读写内存的?

2023-11-12

如果你不知道CPU是如何读写内存的,那你应该好好看看这篇文章,如果你觉得这是一个非常简单的问题,那你更应该好好读读本文,这个问题绝没有你想象那么简单。

一定要读完,闲话少说,让我们来看看CPU在读写内存时底层究竟发生了什么?

1:谁来告诉CPU读写内存

我们第一个要搞清楚的问题是:谁来告诉CPU去读写内存?

答案很明显,是程序员,更具体的是编译器,CPU只是按照指令按部就班的执行,机器指令从哪里来呢?是编译器生成的,程序员通过高级语言编写程序,编译器将其翻译为机器指令,机器指令来告诉CPU去读写内存。

在精简指令集架构下会有特定的机器指令,Load/Store指令来读写内存,以x86为代表的复杂指令集架构下没有特定的访存指令。

精简指令集下,一条机器指令操作的数据必须来存放在寄存器中,不能直接操作内存数据,因此RSIC下,数据必须先从内存搬运到寄存器,这就是为什么RISC下会有特定的Load/Store访存指令,明白了吧?

而x86下无此限制,一条机器指令操作的数据可以来自寄存器也可以来自内存,因此这样一条机器指令在执行过程中会首先从内存中读取数据。

关于复杂指令集以及精简指令集你可以参考这两篇文章《CPU进化论:复杂指令集》与《不懂精简指令集还敢说自己是程序员?》

两种内存读写:

限制我们知道了,是特定的机器指令告诉CPU要去访问内存,不过,值得注意的是,不管是RISC下特定的Load/Store指令还是X86下包含在一条指令内部的访存操作,这里读写的都是北村中的数据,除此之外还要注意到,CPU除了从内存中读写数据外,还要从内存中读取下一条要执行的机器指令

毕竟,我们的计算机设备都遵循冯诺依曼架构:程序和数据一视同仁,都可以存放在内存中。

 现在,我们清楚了CPU读写内存其实是由两个因素来驱动的:

  1. 程序执行过程中需要读写来自内存中的数据
  2. CPU需要访问内存读取下一条要执行的机器指令

然后CPU根据机器指令中包含的内存地址或者PC寄存器中下一条机器指令的地址访问内存。

这不就完了吗?有了内存地址,CPU利用硬件通路直接读内存就好了,你可能也是这样想的,真的是这样吗?别着急,我们接着往下看,这两节只是开胃菜,正餐才刚刚开始。

急性子吃货 vs 慢性子厨师

假设你是一个整天无所事事的吃货,整天无所事事,唯一的爱好就是找一家餐厅吃吃喝喝,由于你是职业吃货,因此吃起来非常职业,1分钟就能吃完一道菜,但这里的厨师就没有那么职业了,炒一道菜速度非常慢,大概需要1小时40分钟才能炒出一道菜,速度比你慢了100倍,如果你是这个吃货,大概率会疯掉的。
而CPU恰好就是这样一个吃货,内存就是这样一个慢吞吞的厨师,而且随着时间的推移这两者的速度差异正在越来越大:
在这种速度差异下,CPU执行一条涉及内存读写指令时需要等“很长一段时间“数据才能”缓缓的“从内存读取到CPU中,在这种情况你还认为CPU应该直接读写内存吗

无处不在的28定律
28定律我想就不用多介绍了吧,在《不懂精简指令集还敢说自己是程序员》这篇文章中也介绍过,CPU执行指令符合28定律,大部分时间都在执行那一少部分指令,这一现象的发现奠定了精简指令集设计的基础。
而程序操作的数据也符合类似的定律,只不过不叫28定律,而是叫principle of locality,程序局部性原理

如果我们访问内存中的一个数据A,那么很有可能接下来再次访问到,同时还很有可能访问与数据A相邻的数据B,这分别叫做时间局部性空间局部性

如图所示,该程序占据的内存空间只有一少部分在程序执行过程经常用到
有了这个发现重点就来了,既然只用到很少一部分,那么我们能不能把它们集中起来呢?就像这样:

集中起来然后呢?放到哪里呢?
当然是放到一种比内存速度更快的存储介质上,这种介质就是我们熟悉的SRAM,普通内存一般是DRAM,这种读写速度更快的介质充当CPU和内存之间的Cache,这就是所谓的缓存。

四两拨千斤
我们把经常用到的数据放到cache中存储,CPU访问内存时首先查找cache,如果能找到,也就是命中,那么就赚到了,直接返回即可,找不到再去查找内存并更新cache。
我们可以看到,有了cache,CPU不再直接与内存打交道了

 但cache的快速读写能力是有代价的,代价就是Money,造价不菲,因此我们不能把内存完全替换成cache的SRAM,那样的计算机你我都是买不起的
因此cache的容量不会很大,但由于程序局部性原理,因此很小的cache也能有很高的命中率,从而带来性能的极大提升,有个词叫四两拨千斤,用到cache这里再合适不过。
天下没有免费的午餐
虽然小小的cache能带来性能的极大提升,但,这也是有代价的。
这个代价出现在写内存时。
当CPU需要写内存时该怎么办呢?
现在有了cache,CPU不再直接与内存打交道,因此CPU直接写cache,但此时就会有一个问题,那就是cache中的值更新了,但内存中的值还是旧的,这就是所谓的不一致问题,inconsistent.
就像下图这样,cache中变量的值是4,但内存中的值是2。

同步缓存更新
常用 redis 的同学应该很熟悉这个问题,可是你知道吗?这个问题早就在你读这篇文章用的计算设备其包含的CPU中已经遇到并已经解决了。
最简单的方法是这样的,当我们更新cache时一并把内存也更新了,这种方法被称为 write-through,很形象吧。
可是如果当CPU写cache时,cache中没有相应的内存数据该怎么呢?这就有点麻烦了,首先我们需要把该数据从内存加载到cache中,然后更新cache,再然后更新内存。

这种实现方法虽然简单,但有一个问题,那就是性能问题,在这种方案下写内存就不得不访问内存,上文也提到过CPU和内存可是有很大的速度差异哦,因此这种方案性能比较差。
有办法解决吗?答案是肯定的。

异步更新缓存
这种方法性能差不是因为写内存慢,写内存确实是慢,更重要的原因是CPU在同步等待,因此很自然的,这类问题的统一解法就是把同步改为异步。

关于同步和异步的话题,你可以参考这篇文章《从小白到高手,你需要理解同步和异步》。
异步的这种方法:当CPU写内存时,直接更新cache,然后,注意,更新完cache后CPU就可以认为写内存的操作已经完成了,尽管此时内存中保存的还是旧数据。当包含该数据的cache块被剔除时再更新到内存中,这样CPU更新cache与更新内存就解耦了,也就是说,CPU更新cache后不再等待内存更新,这就是异步,这种方案也被称之为write-back,这种方案相比write-through来说更复杂,但很显然,性能会更好。

现在你应该能看到,添加cache后会带来一系列问题,更不用说cache的替换算法,毕竟cache的容量有限,当cache已满时,增加一项新的数据就要剔除一项旧的数据,那么该剔除谁就是一个非常关键的问题,限于篇幅就不在这里详细讲述了,你可以参考《深入理解操作系统》第7章有关于该策略的讲解。

多级cache
现代CPU为了增加CPU读写内存性能,已经在CPU和内存之间增加了多级cache,典型的有三级,L1、L2和L3,CPU读内存时首先从L1 cache找起,能找到直接返回,否则就要在L2 cache中找,L2 cache中找不到就要到L3 cache中找,还找不到就不得不访问内存了。
因此我们可以看到,现代计算机系统CPU和内存之间其实是有一个cache的层级结构的

越往上,存储介质速度越快,造价越高容量也越小;越往下,存储介质速度越慢,造价越低但容量也越大。现代操作系统巧妙的利用cache,以最小的代价获得了最大的性能。但是,注意这里的但是,要想获得极致性能是有前提的,那就是程序员写的程序必须具有良好的局部性,充分利用缓存

高性能程序在充分利用缓存这一环节可谓绞尽脑汁煞费苦心,关于这一话题值得单独成篇,关注公众号“码农的荒岛求生”,并回复“todo”,你可以看到之前所有挖坑的进展如何
鉴于cache的重要性,现在增大cache已经成为提升CPU性能的重要因素,因此你去看当今的CPU布局,其很大一部分面积都用在了cache上。

你以为这就完了吗?
哈哈,哪有这么容易的,否则也不会是终面题目了。
那么当CPU读写内存时除了面临上述问题外还需要处理哪些问题呢?
多核,多问题
当摩尔定律渐渐失效后鸡贼的人类换了另一种提高CPU性能的方法,既然单个CPU性能不好提升了,我们还可以堆数量啊,这样,CPU进入多核时代,程序员开始进入苦逼时代。
拥有一堆核心的CPU其实是没什么用的,关键需要有配套的多线程程序才能真正发挥多核的威力,但写过多线程程序的程序员都知道,能写出来不容易,能写出来并且能正确运行更不容易,关于多线程与多线程编程的详细阐述请参见《深入理解操作系统》第5、6两章(关注公众号“码农的荒岛求生”并回复“操作系统”)。
CPU开始拥有多个核心后不但苦逼了软件工程师,硬件工程师也不能幸免。
前文提到过,为提高CPU 访存性能,CPU和内存之间会有一个层cache,但当CPU有多个核心后新的问题来了:

现在假设内存中有一变量X,初始值为2。
系统中有两个CPU核心C1和C2,现在C1和C2要分别读取内存中X的值,根据cache的工作原理,首次读取X不能命中cache,因此从内存中读取到X后更新相应的cache,现在C1 cache和C2 cache中都有变量X了,其值都是2。

接下来C1需要对X执行+2操作,同样根据cache的工作原理,C1从cache中拿到X的值+2后更新cache,在然后更新内存,此时C1 cache和内存中的X值都变为了4。

然后C2也许需要对X执行加法操作,假设需要+4,同样根据cache的工作原理,C2从cache中拿到X的值+4后更新cache,此时cache中的值变为了6(2+4),再更新内存,此时C2 cache和内存中的X值都变为了6。

看出问题在哪里了吗?
一个初始值为2的变量,在分别+2和+4后正确的结果应该是2+2+4 = 8,但从上图可以看出内存中X的值却为6,问题出在哪了呢?
多核cache一致性
有的同学可能已经发现了,问题出在了内存中一个X变量在C1和C2的cache中有共计两个副本,当C1更新cache时没有同步修改C2 cache中X的值

解决方法是什么呢?
显然,如果一个cache中待更新的变量同样存在于其它核心的cache,那么你需要一并将其它cache也更新好。
现在你应该看到,CPU更新变量时不再简单的只关心自己的cache和内存,你还需要知道这个变量是不是同样存在于其它核心中的cache,如果存在需要一并更新。
当然,这还只是简单的读,写就更加复杂了,实际上,现代CPU中有一套协议来专门维护缓存的一致性,比较经典的包括MESI协议等。
为什么程序员需要关心这个问题呢?原因很简单,你最好写出对cache一致性协议友好的程序因为cache频繁维护一致性也是有性能代价的
同样的,限于篇幅,这个话题不再详细阐述,该主题同样值得单独成篇,敬请期待。
够复杂了吧!
怎么样?到目前为止,是不是CPU读写内存没有看上去那么简单?
现代计算机中CPU和内存之间有多级cache,CPU读写内存时不但要维护cache和内存的一致性,同样需要维护多核间cache的一致性

你以为这就完了,NONO,最大的谜团其实是接下来要讲的。
你以为的不是你以为的
现代程序员写程序基本上不需要关心内存是不是足够这个问题,但这个问题在远古时代绝对是困扰程序员的一大难题。
如果你去想一想,其实现代计算机内存也没有足够大的让我们随便申请的地步,但是你在写程序时是不是基本上没有考虑过内存不足该怎么办?
为什么我们在内存资源依然处于匮乏的现代可以做到申请内存时却进入内存极大丰富的共产主义理想社会了呢?
原来这背后的功臣是我们熟悉的操作系统
操作系统对每个进程都维护一个假象,即,每个进程独占系统内存资源;同时给程序员一个承诺,让程序员可以认为在写程序时有一大块连续的内存可以使用。
这当然是不可能不现实的,因此操作系统给进程的地址空间必然不是真的,但我们又不好将其称之为“假的地址空间”,这会让人误以为计算机科学界里骗子横行,因此就换了一个好听的名字,虚拟内存,一个“假的地址空间”更高级的叫法。

进程其实一直活在操作系统精心维护的幻觉当中,就像《盗梦空间》一样,关于虚拟内存的详尽阐述请参见《深入理解操作系统》第七章(关注公众号“码农的荒岛求生”并回复“操作系统”)。
从这个角度看,其实最擅长包装的是计算机科学界,哦,对了,他们不但擅长包装还擅长抽象。
天真的CPU
CPU真的是很傻很天真的存在。
上一节讲的操作系统施加的障眼法把CPU也蒙在鼓里。
CPU执行机器指令时,指令指示CPU从内存地址A中取出数据,然后CPU执行机器指令时下发命令:“给我从地址A中取出数据”,尽管真的能从地址A中取出数据,但这个地址A不是真的,不是真的,不是真的。因为这个地址A属于虚拟内存,也就是那个“假的地址空间”,现代CPU内部有一个叫做MMU的模块将这假的地址A转换为真的地址B,将地址A转换为真实的地址B之后才是本文之前讲述的关于cache的那一部分。

你以为这终于应该讲完了吧!
NONO!
CPU给出内存地址,此后该地址被转为真正的物理内存地址,接下来查L1 cache,L1 cache不命中查L2 cache,L2 cache不命中查L3 cache,L3 cache不能命中查内存。
各单位注意,各单位注意,到查内存时还不算完,现在有了虚拟内存,内存其实也是一层cache,是磁盘的cache,也就是说查内存也有可能不会命中,因为内存中的数据可能被虚拟内存系统放到磁盘中了,如果内存也不能命中就要查磁盘
So crazy,限于篇幅这个过程不再展开,《深入理解操作系统》第七章有完整的讲述。
至此,CPU读写内存时完整的过程阐述完毕。

总结
现在你还认为CPU读写内存非常简单吗?
这一过程涉及到的硬件以及硬件逻辑包括:L1 cache、L2 cache、L3 cache、多核缓存一致性协议、MMU、内存、磁盘;软件主要包括操作系统。
这一看似简单的操作涉及几乎所有计算机系统中的核心组件,需要软件以及硬件密切配合才能完成
这个过程给程序员的启示是:

1),现代计算机系统是非常复杂的;

2), 你需要写出对cache友好的程序

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CPU是如何读写内存的? 的相关文章

  • 华为机顶盒系统时间同步服务器,华为悦盒主时间同步服务器地址

    华为悦盒主时间同步服务器地址 内容精选 换一换 华为云存储容灾服务 简称SDRS 提供了虚拟机级别的容灾保护 当主站点故障的时候 虚拟机可以在备站点迅速恢复 以确保业务的联系性 来自 产品 边缘节点既可以是物理机 也可以是虚拟机 边缘节点需
  • Linux下C/C++语言gdb调试方法

    1 gdb参数列表 启动程序准备调试 gdb your proceduce 或者先输入gdb 然后输入 file your proceduce 然后使用run或者r命令开始程序的执行 也可以使用 run parameter将参数传递给该程序
  • 核酸检测安排

    题目描述 在系统 网络均正常的情况下组织核酸采样员和志愿者对人群进行核酸检测筛查 每名采样员的效率不同 采样效率为N人 小时 由于外界变化 采样员的效率会以M人 小时为粒度发生变化 M为采样效率浮动粒度 M N 10 输入保证N 10 的结
  • 软件测试工程师(4k~6k)的工作怎么找?转行IT人特别是应届生得好好看看这篇文章了...

    前言 作为一个入行软件测试10多年的老兵来说 最初我的工作也不是做软件测试的 只是一个偶然后机会可以转到这个行业 所以就豪不犹豫的转到这个行业 虽然前期会感觉有点压力 毕竟没有真正的做过 但是只要在工作中保持积极乐观的态度 多问 多学 多实
  • C语言数据结构篇——用栈实现四则运算

    作者名 Demo不是emo 主页面链接 主页传送门创作初心 舞台再大 你不上台 永远是观众 没人会关心你努不努力 摔的痛不痛 他们只会看你最后站在什么位置 然后羡慕或鄙夷座右铭 不要让时代的悲哀成为你的悲哀专研方向 网络安全 数据结构 每日
  • 【RocketMQ】NameServer总结

    NameServer是一个注册中心 提供服务注册和服务发现的功能 NameServer可以集群部署 集群中每个节点都是对等的关系 没有像ZooKeeper那样在集群中选举出一个Master节点 节点之间互不通信 服务注册 Broker启动的
  • “左三圈右三圈”,莫言开收割机 收割大批网友喜爱

    昨晚十点 莫言公众号如期更新 半夜就有网友在朋友圈里奔走相告 莫言为大家表演开联合收割机啦 看完莫言紧张又努力开收割机的视频 网友直呼 莫言老师抓紧方向盘使劲转动的样子太可爱了 爱了 爱了 在大家的印象中 莫言是深邃而丰腴的大作家 但这个视
  • [STM32F1]STM32上的DWT与延时实现

    对于做单片机程序开发来说 定时管理的需求非常普遍 不管是 系统滴答定时器systick计数器延时或者是通过外设定时器timer的向上向下等计数来延时 甚至在精度要求不高的地方还可以通过变量自加判断来延时 这都是一种延时管理实现的方法 但是对
  • java并发的概念

    1 并发的概念 并发 concurrency 指在同一时刻只能有一条指令执行 但多个进程指令被快速的轮换执行 使得在宏观上具有多个进程同时执行的效果 但在微观上并不是同时执行的 只是把时间分成若干段 使多个进程快速交替的执行 2 并行的概念

随机推荐

  • Ubuntu配置中文环境

    用了一段时间的英文开发了想起来要不换中文试试 所以闲暇之余配置了一个中文 做了一个小记录 这个是英文的环境下面的界面 在安装前简称系统的网络 和源是否正常 检查网络ping www baidu com 检查源 cat etc apt sou
  • 老板的思维模式:投资与浪费

    有人说 人生最大的投资 不是房子 不是股票 是人 是跟什么人交往 跟随什么人 交什么样的朋友 其实就是你投资什么人 而这是对人生影响最大的 钱不会给你机会 股票不会 房子也不会 只有人会给你机会 当你需要帮助的时候 只有跟你要好的人会帮你
  • 企业运维实践-如何在K8S集群环境Gitlab+Jenkins+Jmeter+Grafana技术中实现自动化分布压力测试数据展示...

    关注 WeiyiGeek 公众号 设为 特别关注 每天带你玩转网络安全运维 应用开发 物联网IOT学习 本章目录 0x00 前言简述 0x01 安装配置 在 Windows 中安装 Apache jmeter 工具 以二进制方式安装Helm
  • STM32 定时器

    include timer h include led h 本程序只供学习使用 未经作者许可 不得用于其它任何用途 Mini STM32开发板 通用定时器 驱动代码 正点原子 ALIENTEK 技术论坛 www openedv com 修改
  • 关于char类型变量输入与输出的区别

    笔者前几天看到了一个小项目 请输入一个小写字母 输出对应的大写字母 乍一看挺简单 可实际操作却难倒了我 直到我打开看了老师的视频之后 我才恍然大悟 char的输入其实输入的永远是数字 没有单纯的字符 与此同时char的输出却有两种形式 c对
  • covariance matrix r语言_时间序列分析

    这是关于时间序列的第N篇文章 本文将介绍ARIMAX模型 简单来说就是在ARIMA的基础上增加一个外生变量 ARIMAX和ARIMA相比在理论上没有太多新的内容 所以本文直接介绍在R里怎么一步一步跑ARIMAX 在阅读这篇文章前 需要对AR
  • MySQL 的主从复制原理详解高级

    首先要明白为什么要用 mysql 的主从复制 1 在从服务器可以执行查询工作 即我们常说的读功能 降低主服务器压力 主库写 从库读 降压 2 在从主服务器进行备份 避免备份期间影响主服务器服务 确保数据安全 3 当主服务器出现问题时 可以切
  • eclipse中package,source folder和folder

    在eclipse的Package explorer中 如下图所示 Source folder 存放Java的源代码 eclipse会自动编译里面的文件 以 来进行文件夹的分级 默认为src文件夹 Package 一般位于source fol
  • 【深度学习】 Python 和 NumPy 系列教程(六):Python容器:4、字典Dictionary详解(初始化、访问元素、常用操作、常用函数、遍历、解析)

    目录 一 前言 二 实验环境 三 Python容器 Containers 0 容器介绍 4 字典 Dictionary 0 基本概念 1 初始化 a 使用 创建字典 b 使用dict 函数创建字典 2 访问字典元素 a 使用方括号 b 使用
  • nexus下载安装

    进入官网http www sonatype org 点击Jion Now 展开downloads 选择Nexus Repository Manager OSS 目前已经更新到3 X了 这里暂且还是选2 X的吧 下载完 解压 cmd打开命令提
  • 【软件工程】第五章 结构化设计

    5 1 结构化设计的概念 5 1 1 设计的定义 何谓设计 一种软件开发活动 定义实现需求规约所需的软件结构 目标 依据需求规约在一个抽象层上建立系统软件模型 包括软件体系结构 数据和程序结构 以及详细的处理算法 给出软件解决方案 产生设计
  • 欧拉回路【总结】【题解】

    题目 欧拉回路 UOJ 欧拉回路 Liuser s OJ 题目描述 有一天一位灵魂画师画了一张图 现在要你找出欧拉回路 即在图中找一个环使得每条边都在环上出现恰好一次 一共两个子任务 无向图 有向图 输入格式 第一行一个整数 t 表示子任务
  • vue项目启动后,js-base64依赖报错Cannot read properties of null (reading ‘replace’)

    cannot read properties of null reading replace 关于这种乱七八糟的问题 咱也不敢说 在哪也不敢问 项目运行之后 有一些警告 都是一些依赖版本的问题 平时也能直接给运行起来 这次就是项目可以运行起
  • rabbitmq+springboot实现幂等性操作

    文章目录 1 场景描述 1 1 场景1 1 2 场景2 2 原理 3 实战开发 3 1 建表 3 2 集成mybatis plus 3 3 集成RabbitMq 3 3 1 安装mq 3 3 2 springBoot集成mq 3 4 具体实
  • 阿里云服务器使用xshell连接

    阿里云服务器使用xshell连接 当购买了第一次阿里云服务器时 如何使用xshell连接 其实是非常简单的 1 登录阿里云控制台 1 是你的阿里云服务器所在地址 2 是公网IP 将来远程连接时需要使用 3 是设置远程连接的密码 用户名默认r
  • 在x86和arm编译libmodbus

    编译libmodbus 下载路径 1 编译准备 sudo apt get install libtool autogen sh 2 arm编译 autogen sh mkdir install configure ac cv func ma
  • 电压电流的驱动能力分析以及计算方法

    文章为笔者学习过程中看到的 感觉帮助较大 分享出来希望能帮助到大家 在电子电路中为什么有的地方电压会被拉低2 驱动能力是什么意思 如何提高驱动能力 在很多资料上看到说驱动能力不够是因为提供的电流太小 为什么不说电压呢 在很多限制的条件都是电
  • BIOS开启虚拟化技术

    什么是BIOS BIOS 是一个内置于个人计算机的程序 当您打开计算机时该程序启动操作系统 也称为系统固件 BIOS 是计算机硬件的一部分 不同于 Windows 怎么进入BIOS 电脑进入BIOS的方法各有不同 通常会在开机时 显示电脑l
  • atoi函数源代码

    atoi函数源代码 isspace int x if x x t x n x f x b x r return 1 else return 0 isdigit int x if x lt 9 x gt 0 return 1 else ret
  • CPU是如何读写内存的?

    如果你不知道CPU是如何读写内存的 那你应该好好看看这篇文章 如果你觉得这是一个非常简单的问题 那你更应该好好读读本文 这个问题绝没有你想象那么简单 一定要读完 闲话少说 让我们来看看CPU在读写内存时底层究竟发生了什么 1 谁来告诉CPU