头图

一切的起源 图灵机

图灵机主要由数据存储单元,控制单元,运算单元和一个可读写外部数据的读写头几部分构成。
图灵机工作需要有一条纸带,纸带上面布满格子,可以在格子上面记录字符,字符可分为数据字符和指令字符;纸带穿过图灵机并不断向前移动;图灵机上的读写头依次读取纸带格子上的字符,根据控制单元区分读取的字符属于数据还是指令,当读到数据字符时,将字符存储到存储单元中,当读到指令字符时,运算单元会将存储单元中的数据读取出来并进行相应运算,并将结果通过读写头写入纸带的下一个格子中。
image.png
图灵机的基本工作模式跟如今的计算机是一样的,数据和指令存在存储器中(纸带和存储单元),处理器读取后运算得出结果。计算机中使用cpu进行指令计算,存放数据的存储器我们常听的有磁盘、SSD、内存。但cpu并不直接从这些存储器中读取并执行指令,而是采用分级缓存策略。

存储器分级

为什么需要分级

我们比较熟悉的磁盘,数据在断电之后还能保存着,而且磁盘的存储空间较大,通常能有上T容量,但其数据读取速度极慢;内存的读取速度虽然比磁盘快了将近100倍,但跟cpu的执行速度相比,还是属于”龟速“。此外,内存是按在主板上的,数据通过电路板传输到cpu上,数据传输的耗时相对于cpu执行速度来说也是不可忽视的。

存储器体积越小,其存储容量就会受到限制;读写速度越快,能耗和成本也会越高;其次,存储器距离cpu越远,数据传输也越大。所以,目前而言,使用单一存储器无法让存储器中的数据跟的上cpu的处理速度。

计算机采用的方案是将存储器分级,将cpu使用频率越高的数据,存放在读写速度越快,距离cpu更近的存储器(缓存)中;将使用频率较低的数据存放在读写速度较慢,距离cpu较远,但存储容量较大,成本较低的存储器中。 这样,cpu读取数据时,直接先从缓存中读取,缓存中不存在再从距离更远的存储器中读取。

分级缓存方案的可行性在于计算机存在局部性原理,试想下我们平时写的代码程序,运算用的最多的是for循环,然后对定义的几个变量进行计算读写。所以,cpu执行一个程序的时候,有几个数据区域的读写频率是比较高的。所以,可以将这些「热点」区域的数据缓存起来,下次读取的时候就会快很多。据统计,存储器缓存命中率能达到95%,也就是只有5%的数据会穿透到内存。

存储器分级策略

通常,存储器分成以下几个级别:

  • 寄存器
  • CPU cache:

    • L1-cache
    • L2-cache
    • L3-cache
  • 内存
  • 磁盘/SSD

磁盘/SSD

SSD/磁盘是距离CPU最远,读取速度最慢的一类存储器,优点在于成本较低,断电后数据还在。其中SSD是我们常说的固态硬盘,结构与内存类似,读写速度比内存慢10-1000倍;磁盘读取速度更慢,比内存慢100W倍左右,随着SSD的普及,已经慢慢被取代了。

内存

内存是插在主板上,与CPU有一段距离,CPU通过主板总线读取内存中的数据,造价比磁盘稍贵,但读取速度比磁盘快,速度大概在200-300个CPU周期;容量方面,个人电脑的内存一般是8-16G,服务器上的内存可以达到几个T。

CPU周期:一条指令可分为取指令,执行指令等若干个阶段,每个阶段完成所需的时间成为CPU周期。

CPU cache (CPU 高速缓存)

CPU cache 存在于 CPU 内部,CPU cache 可分为 L1 (一级缓存)、L2 (二级缓存)、L3 (三级缓存),CPU 的每个核都有各自的 L1 和 L2 缓存,同一个 CPU 的多个核共享一个 L3 缓存。

  • 与 CPU 距离:L1 < L2 < L3
  • 容量: L1(几十~几百 KB)<L2 (几百 KB~几 MB) < L3 (几 MB~几十 MB)
  • 读写速度: L1(2-4CPU 周期) > L2 (10-20CPU 周期) > L3 (20-60CPU 周期)

image.png
L1 缓存划分了指令区和数据区,下文会解释

需要注意的是,cpu 缓存中每个缓存的最小单位是内存的一个内存块,而不是缓存一个变量;cpu 缓存和内存的映射方式有很多种,类似于 cache 行号 = 内存页号 mod cache 总行数;这样,先根据内存地址计算出地址所在内存页号,再通过映射关系算出 cache 行号,如果存在缓存中,直接获取数据即可,如果不存在再到内存中获取。
image.png

寄存器

寄存器是 CPU 实际进行指令读写的地方,是距离 CPU 最近的存储器,读写速度也是最快,能在半个 CPU 周期完成读写;一个 CPU 中寄存器数量在几十到几百个之间,每个寄存器容量很小,只能存储一定字节(4-8 个字节)的数据。

32 位 CPU 中大多数寄存器可以存储 4 个字节64 位 CPU 中大多数寄存器可以存储 8 个字节

寄存器根据用途不同,可分为好几类,为了便于后面指令执行过程学习,我们先了解以下几类:

  • 通用寄存器:用于存储程序参数数据。
  • 指令寄存器:每条 CPU 执行的指令,会先从内存中读入指令寄存器中,然后再让 CPU 读取执行。
  • 指令指针寄存器:存放着 CPU 下一条要执行的指令所在的内存地址,CPU 根据指令指针寄存器中的指令内存地址,将指令读入指令寄存器中。指令指针寄存器也成为 IP 寄存器。
  • 段寄存器:为了可访问更大的物理空间,CPU 通过基础地址 + 偏移量定位一个物理内存地址。段寄存器中存储的是基地址信息。CS 是存放指令地址的一个段寄存器,与 IP 寄存器一起定位指令在内存中的地址。

    假设一个寄存器最大存储 4 字节数据,4 字节 = 4*8=32 位,值表示范围:0~(2^32) -1,换算单位为 4G,也就是这个寄存器最大能查找 0-4G 范围的地址,但我们之前提到的内存容量可达几 T,所以,直接通过一个寄存器无法表示全部范围的内存地址。采用 “基础地址 + 偏移地址 = 物理地址” 的寻址模式,可极大扩大内存寻址能力。例如:32 位的基础地址左移 32 位,再加上 32 位的偏移地址,可表示 64 位(16EiB)的内存地址。需要注意的是,计算机的最终寻址范围是由下面介绍的地址总线决定的。

总线 - CPU 与外界的桥梁

按上面的存储器分级,数据先从磁盘加载到内存中,然后被读取到 CPU 内部的高速缓存和寄存器中,CPU 读取寄存器进行处理。其中,CPU 和 CPU cache 之间的数据读写是在 CPU 内部中完成的,CPU 对内存的读写则是通过主板上的总线完成的。

总线可以看成是多根导线的集合,通过控制导线电压的高低来传递数据,高电压是 1,低电压是 0。
image.png
根据传输信息的不同,总线分为地址总线,数据总线和控制总线

试想 “向内存 3 位置读取数据” 这一条读指令包含了几个信息:

  • 操作的内存位置是 3(地址信息)
  • 操作的命令是读命令(控制信息)
  • 数据传输(数据信息)

3 类总线分别负责对应信息的传输:CPU 通过地址总线将要操作的内存地址信息传递给内存;通过控制总线发出内存读命令;内存将数据从数据总线传入 CPU。
image.png

地址总线

讲地址总线之前,我们先讲讲存储器地址的划分。存储器会被划分为若干个存储单元,存储单元从零开始编号,这些编号可以看做是存储单元在存储器中的地址。

每个存储单元由 8 个位 (bit) 组成,也就是可以存储一个字节的数据;假设一个存储器有 128 个存储单元,可以存储 128 个字节 (Byte)。

CPU 通过地址总线来指定存储单元,地址总线的线数,决定了对内存的寻址范围。比如,一个 CPU 有 16 根地址总线,最多可以寻找 2 的 16 次方个内存单元。

假设一个 16 位的 CPU 有 20 条地址总线,16 位的 CPU 如何一次性给出 20 位的地址呢?

其实答案前面已经给出了,CPU 内部会通过「基础地址」+「偏移地址」的方法合成一个 20 位的地址。

image.png

数据总线

CPU 与内存或其他器件通过数据总线进行数据传输,数据总线的宽度 (总线条数) 决定了 CPU 与外界的数据传输速度。8 根数据总线一次可传输一个字节 (8bit) 的数据,16 根数据总线一次可传输两个字节 (16bit)。

控制总线

CPU 对外部器件的控制是通过控制总线来进行的,多少根控制总线,意味着 CPU 对外部器件有多少种控制,所以控制总线的宽度决定了 CPU 对外部器件的控制能力。

指令执行

了解了各种存储器和总线,我们再来看看程序是如何从磁盘加载到内存然后被 CPU 执行的。

我们编写的程序需要先经过编译器翻译成 CPU 认识的指令,这个过程称为指令的构造。程序启动时,会将程序的指令和数据分别存在两个内存段中。同时,PC 指针(IP 寄存器 + CS 寄存器)会指到指令段的起始地址(就是将起始地址赋值到 PC 指针上),表示 CPU 将从这个地址开始读取内存中的指令并执行。
image.png

指令解析

指令先被读取到指令寄存器中,CPU 取出执行时,需要先对指令进行解析。

我们都知道,内存中存放的内容都是二进制类型(上面的指令我们写成 16 进制),cpu 读取到要执行的指令后,会先对二进制的指令进行解析。以上面 “0x8c400104” 为例,拆分成二进制:

image.png
上面指令分成操作码、寄存器编号、内存地址三部分:

  • 最左边 6 位,称为操作码,“10011” 表示 load 指令。
  • 中间 4 位,指定了寄存器的编号,“0001” 表示 R1 寄存器。
  • 最后的 22 位表示要操作的内存地址。

所以,这条指令是指将指定内存地址的内容加载到寄存器 R1 中。
总结一下,程序执行时:

  1. 程序的指令和数据分别存入内存指令段和数据段中,PC 指针指到指令段的起始地址。
  2. CPU 读取 PC 指针指向的指令存入指令寄存器中。
    a. CPU 通过地址总线指定要访问的内存地址;通过控制总线发送 “读指令”。
    b. 内存通过数据总线将数据传入 CPU,CPU 将这个数据存到指令寄存器中。
  3. CPU 解析指令寄存器中的指令内容。
  4. CPU 通过运算单元和控制单元执行指令。
  5. PC 指针自增,指向下一条指令内存地址。

所以,取址、译码、执行,这是一个指令的执行周期,所有指令都会严格按照这个顺序执行。

指令预读

CPU 执行指令的速度是非常快的,但内存的读写是非常慢的,所以,如果从内存中一条条读取指令再执行的话,指令执行周期会变得很慢。

前面我们学到,CPU 内部还有三级缓存,所以,我们可以将内存中的多条指令一次性先读到读写速度较快的 L1 缓存中,这样,取址速度就能跟的上 CPU 的指令执行速度了。

同时,为了避免数据缓存覆盖指令缓存影响指令执行,我们可以将 L1 缓存划分为指令区和数据区。

思考下 L2 和 L3 需要划分指令区和数据区吗?其实是不需要的,因为 L2 和 L3 并不需要协助指令预读。

如何更快的执行指令

为了更快的执行指令,我们需要使用 CPU 的指令流水线技术。

在刚才的流程中,取指,解码的时候运算单元是空闲的,为了提高指令处理速度,需要让运算单元就可以一直处于运算中。我们可以使用 CPU 的指令流水线技术,在第一条指令完成取址进行译码时,第二条指令立刻进行取址,依次类推,这样,在上一条指令完成执行后,下一条指令也完成译码可以进行执行了。
image.png

一句话总结

程序存储在存储器中,cpu 读取指令并进行执行计算。

由于 cpu 的指令执行速度极快,目前没有存储器能同时满足读写速度快,散热小,能耗低,容量大等要求,所以采用存储器分级策略,使用多级缓存来匹配上 cpu 的执行速度。

cpu 与内存之间的数据传输通过主板上的总线来完成。通过地址总线将要操作的内存地址信息传递给内存;通过控制总线发出命令类型;内存将数据从数据总线传入 CPU。

寄存器是 cpu 直接读取指令和参数数据的存储器。寄存器按用途可分为好几类。对于数据,会先将数据读到通用寄存器中,之后 CPU 从通用寄存器中读写数据;对于指令,CPU 会先根据 CS 段寄存器和指令指针寄存器共同指向的指令内存地址获取指令,并将指令存入指令寄存器中,之后 CPU 再从指令寄存器中读取执行。

指令的执行包括取址、译码、执行。为了避免 CPU 每次获取指令都得从内存中获取,可以先将指令预读到 CPU L1-Cache 中;同时,为了让 CPU 的计算单元一直处于运算状态,可以使用流水线技术。

写在最后

喜欢本文的朋友,欢迎关注公众号「会玩code」,专注大白话分享实用技术
image.png


BestAIHub
3 声望1 粉丝