电脑配置|硬件知识|电脑故障|电脑技巧|电脑应用|组装电脑

CSAPP(六)——存储器层次结构

分层概述

不同层次的存储器的常见访问时钟周期

存储器 时钟周期 CPU寄存器 0 高速缓存 4~75 主存 上百 磁盘 几千万

除非设计非常失败,计算机程序大多数都会在一段时间内持续的访问某个局部的数据,这称为局部性原理。所以存储空间更小,性能更高的存储器通常作为存储空间稍大,性能稍低的存储器的缓存,用于缓存目前正在被频繁访问的局部数据。

存储技术

SRAM(静态RAM)

SRAM是一个将位存储在由6个晶体管的电路来实现的具有双稳态的存储器单元中。左稳态和右稳态代表位的两种状态。

SRAM是稳定的,只要有电,它就会倾斜到一边。下面是SRAM和DRAM的特性对比。

SRAM造价高昂,速度快,用在高速缓存上,DRAM相对低廉,速度稍慢,用在主存上。

DRAM(动态RAM)

如下,是一个16x8的DRAM芯片:

一个DRAM芯片由\(d\)个超单元(supercell)组成,图中就是16个超单元。超单元被组成\(r\)\(c\)列的长方形阵列,图中是4行4列。每个超单元中包含\(w\)个DRAM的基本单元,每个单元可存储一位数据。一个\(d\times w\)的DRAM芯片能存储\(dw\)个位。

内存控制器是一组电路,它通过引脚(pin)连接到DRAM芯片(内存控制器可能连接很多个DRAM芯片,稍后会看到),每个引脚传输一位数据,图中有两个\(addr\)引脚用来控制读写的超单元所在的行列(所以行列是共用一组引脚的),有八个\(data\)引脚用来传输一个超单元中所存储的\(w\)个位(这里是8个)。所以,\(w\)是DRAM芯片读取的基本单位。下图是从一个16x8的DRAM芯片中读取超单元(2,1)的内容的图示。

先通过\(addr\)引脚发送RAS(行访问选通脉冲),然后DRAM会将行2的所有内容都复制到内部行缓冲区,然后发送CAS(列访问选通脉冲),DRAM会从内部行缓冲区中读出列1返回给内存控制器。

二维地址可以缩小地址引脚数量,但由于必须发送两次才能确定一个超单元,所以增加了读写延时。

Core i7使用240个引脚的双列直插内存模块,以64位大小从内存控制器和内存间传递数据。

内存模块

内存模块可能由多个DRAM芯片组成,下面是一个具有8个大小为64Mbit的DRAM芯片的内存模块,每个DRAM芯片是\(8M\times 8\)的,也就是每个内存芯片中有\(8M\)个超单元,每个超单元有\(8\)位数据,总共就是\((8M \times 8bit)\times 8 = 64MB\)的大小。

上图中,每个DRAM芯片的每个超单元\((i, j)\)存储主存的一个字节,然后每个DRAM芯片的超单元\((i, j)\)连接起来,就组成了一个64位字。

所以:

  1. 内存控制器接收到读取地址A处的64位字
  2. 内存控制器将地址A转换成包含数据的内存模块k以及超单元地址\((i, j)\),将其发送给内存模块k
  3. 内存模块将\((i, j)\)广播到每个DRAM芯片,DRAM芯片输出它们的8位数据
  4. 内存模块将每个DRAM芯片的8位数据聚合成64位数据,返回给内存控制器

对于CPU来说,内存是一个巨大的一维数组,而对于内存控制器来说,它是二维数组。(实际上如果算\((k,i,j)\)的话应该是三维数组)

增强的DRAM

  1. 快页模式:传统DRAM的行缓冲区用完即扔,下次再复制。快页模式不会直接扔,这是为了在连续访问相同行时使用之前的行缓冲。并且,可以通过不传RAS只传CAS来表示读取和之前相同的行。
  2. ...

访问主存

CPU和主存之间通过总线来传输数据,读写的步骤称为总线事务。总线只是一组并行的导线,具体的细节取决于设计,但不论怎么设计,它们都是用来传递地址、数据和控制信号的。不同类型的信号可以用不同的总线,不同的设备也可以用相同的总线。下图中CPU和IO桥通过系统总线连接,IO桥和主存通过内存总线连接。

由于中文版PDF是扫描的,所以图片上的总线已经模糊到无法分辨,这里只能放上原书的图

下面是一条从内存地址A读数据到%rax中的指令movq A, %rax的执行过程:

对于CPU来说,它只是将地址放到系统总线上并从系统总线读出字作为读取的结果。而对于IO桥来说,它将来自系统总线的信号翻译成内存总线的信号并放到内存总线上,并从内存总线中读出字,翻译成系统总线的信号并放到系统总线上。对于主存来说,它从内存总线读出数据,操作DRAM芯片,读出字并将数据放回内存总线。

磁盘存储

磁盘的物理结构我记得好像在以往的某些笔记中记录过,这里只是简单记记。

操作时间

  1. 寻道时间:因为读取磁盘时,磁盘臂可能不在包含数据的扇区所在的磁道上,所以要先将磁盘臂移动道对应的磁道上。这个时间的平均值通常为3到9ms,最大时间可高达20ms。
  2. 旋转时间:寻道之后,磁盘臂所在的磁道已经包含要操作的扇区了,但是这个扇区并不一定在磁头下面,所以要将磁盘旋转,将要操作的扇区移动到磁头下。
  3. 传送时间:实际用来传送数据的时间。

逻辑磁盘块

磁盘作为一种精密的机械装置,它的原理十分复杂,在操作系统层面,没有人愿意处理盘片、磁道、扇区、柱面这种概念,所以磁盘被抽象成了若干个块,每个块都是一个扇区的大小。磁盘中的一个硬件设备——磁盘控制器——维护着块和实际物理存储的三元组——(盘面, 磁道, 扇区)——之间的关系。

所以内存控制器提供复杂的内存模块和其中的DRAM芯片的一个抽象,它对CPU提供一个内存是一维线性数组的视图;磁盘控制器提供磁盘复杂的机械结构的一个抽象,它对操作系统提供一个磁盘就是由若干个大小相等的块组成的线性序列的视图。

IO总线

IO总线用来将一切IO设备连接到CPU和主存,不像内存总线,一些IO总线被设计成CPU无关的,如PCI总线,这给IO总线带来了更多通用性。

IO总线可以容纳种类繁多的IO设备,如下图,有三种不同的IO设备连接到IO总线,分别是USB、图形适配器和主机总线适配器(用于连接磁盘):

访问磁盘

CPU使用一种被称作内存映射IO的技术来向IO设备发送命令。

就像Linux中把一切设备都抽象成文件一样,内存映射IO技术将一切IO设备抽象成一个内存地址,这使得CPU能以与读写内存一致的方式来操作IO设备。地址空间中专门预留出一部分这样的地址,每一个地址称为一个IO端口,一个设备与一个或多个IO端口相关联。

假设磁盘控制器映射到端口0xa0,CPU可能会分三次向这个地址写入来进行一个磁盘读取,第一次写入一个命令字,告诉磁盘发起一个读以及一些其它参数,第二次指定要读的逻辑块号,第三次指定读到的内存地址,如下图:

CPU发起后,磁盘进行处理,在这期间CPU通常会去执行些其他任务以免数千万个时钟周期被浪费。然后磁盘会将逻辑块号转换成扇区地址,读该扇区并将内容制品传送到主存,如下图所示。这种数据传输叫DMA(直接内存访问),如果不使用DMA,则需要CPU来干涉,这就造成数据在系统总线和内存总线中进行多次转发

当DMA传送完成,磁盘控制器给CPU发送一个中断,因为CPU现在可能在干其他工作,它必须中断CPU手中的工作告诉它某个程序请求的磁盘读取完成了,你看看接下来该怎么搞。如下图:

固态硬盘

固态硬盘与机械硬盘有着完全不一样的原理和物理结构,但和机械硬盘具有一样的行为,它接收逻辑磁盘块号,并通过闪存翻译层将其翻译为底层闪存块的访问。闪存翻译层就相当于机械硬盘中的磁盘控制器。

一个固态硬盘由一系列闪存块组成,闪存块中又包含一系列页。数据以页为单位读写,页的常见大小为512Bytes~4KB,块的常见大小为16KB~512KB

固态硬盘很容易被磨损,一个块大约在被重复写100000次后,块就不能再使用了。而在写页时,只有该页所属的块被整个擦除才能写,擦除后,块中的每个页可以不需要擦除再写一次。

固态硬盘的写操作比较复杂,所以写操作性能往往比读低。固态硬盘的闪存翻译层中的平均磨损逻辑会将写操作均匀的分布在所有块上,以延长固态硬盘的寿命。

存储器层次结构

k层作为k+1层的缓存,k层缓存着k+1层中所有块的一个子集。数据总是以块位单位在不同层之间传送,但不同层之间的块大小不同,比如L0到L1之间通常使用一个字大小的块,而L1和L2之间通常是几十个字节,L4和L5之间通常是几百或几千字节。这是因为越底层设备的访问时间就越长,所以倾向于选择较大的块。

高速缓存

通用高速缓存存储器组织结构

高速缓存用于缓存主存中的数据,由于CPU将主存看作一个线性的字节数组,所以每个字节具有一个地址。CPU它会将想要访问的字节地址A发送给高速缓存,高速缓存中如果有这个字节数据就直接返回给CPU。高速缓存完全由硬件控制,这意味着我们必须提供一种简单的方式在具有\(m\)位地址长度的计算机上将地址简单快速的映射到高速缓存的一个位置。

如上图a,在一台存储器地址有\(m\)位的计算机上,高速存储器被分成了\(S\)个组,每个组被分了\(E\)个高速缓存行,每个缓存行中保存有\(B\)字节的数据。所以整个高速缓存的容量\(C=S\times E\times B\)字节,每个字节称为一个高速缓存块(\(SEB\)都必须是2的幂次的大小,所以\(C\)也是)。然后,高速缓存行中还有一个有效位和\(t\)个标记位。

这样划分高速缓存是因为为了方便将存储器地址映射到高速缓存中,如上图b,\(m\)位存储器地址被分成了三份:

  1. 组索引具有\(s\)位,它被看作一个无符号整数,它将地址映射到\(S\)个高速缓存组中的一个上,所以\(S=2^s\)
  2. 标记具有\(t\)位,它也被看作一个无符号整数,它将地址映射到\(E\)个高速缓存行中的一个上。不过和\(s\)不一样的是,\(t\)的长度并不取决于高速缓存行的个数,它的长度常常远远大于能够容纳\(E\)个高速缓存行的二进制数的最小位数。相反,\(t=m-(s+b)\),也就是说,\(t\)的长度完全看去掉\(s\)\(b\)后还剩多少。不同于组索引的一一映射,这里\(E\)个缓存行肯定容纳不下那么多的数据,所以缓存行中就有一个标记位用来记录当前行容纳的是什么数据,只有当一个行设置了有效位并且它的标记位和地址中的标记位匹配时才认为这一行包含想要读取的数据。
  3. 块偏移具有\(b\)位,它代表当前地址所代表的内存字节数据缓存在缓存行的数据部分的偏移量。换句话说,它代表当前这个数据存在缓存行的第几个字节上。

这样,通过将一个地址分成三部分,然后将每部分映射到一个高速缓存组->高速缓存行->字节偏移量上,这个地址很轻松并且快速的被保存到了高速缓存中。

习题6.9

直接映射高速缓存

上一篇:

下一篇: