王道考研操作系统第三章存储系统


在上述金字塔中,越上层的速度越快、容量越小、价格越高。
当我们用手机打开微信,出现微信的地球页面加载时,其实就是把微信从辅存放入主存中,然后CPU就会开始运行微信相关的代码。辅存中的数据要调入主存后才能被CPU访问
主存之上还有Cache高速缓存存储器,上述箭头表示 读/写数据,之所以要在CPU和主存之间加Cache,是因为虽然主存的读写速度已经很快了,但是还是远远跟不上CPU的处理速度。
主存和辅存之间的交换是由硬件+操作系统来实现的,操作系统需要实现页面置换算法来决定把哪些数据从主存换出到辅存。主存和Cache由硬件自动完成。

第一种分类的方式就是按照层次结构来分类,我们需要重点关注的是中间三层高速缓存、主存、辅存。高速缓存和主存可以直接被CPU读写,而辅存里的数据只有调入到主存之后才可以被CPU访问。
无论是什么存储器,终归都是用来存储二进制数据的。根据存储二进制位的存储介质不同,我们分为:
随机存取存储器(RAM Random Access Memory)
顺序存取存储器(SAM Sequential Access Memory)
直接存取存储器(DAM Direct Access Memory)
我们将顺序存取存储器和直接存取存储器也称为串行访问存储器
- 串行访问存储器:读写某个存储单元所需时间与存储单元的物理位置有关
上述三种存储器都是指定数据的存储地址,然后直接去那个地址当中进行读写。而下面的相联存储器是指明数据内容,根据数据内容来查找位置。前者是根据地址访问,后者是根据内容访问。



这一节主要学习半导体元件存储二进制数据的原理,通过半导体元件的组合构成存储芯片,由于存储芯片里面存储了很多个字的数据,因此存储芯片必须提供一个寻址的功能。

一个存储器可以分为存储体、MAR、MDR三大部分。MAR是地址寄存器,MDR是数据寄存器,这三部分会在时序控制逻辑电路的控制下工作。存储体是用来存储二进制数据0和1的,而存储体又是由一个个存储单元构成,存储单元又是由一个个存储元构成,用一个存储元就可以存储一位二进制0或1。一个存储元又包含两个半导体元件,分别为电容和MOS管。

我们可以将MOS管看成是用电控制的开关,当电压足够时MOS就可以连通导电,若不加电压或者电压不足,那么就断开不导电。这就是叫做半导体的原因,既不是导体也不是绝缘体,在一定条件下既可以转换为导体,也可以转换为绝缘体。
电容由两个金属板和中间的绝缘体构成,如图下方的金属板接地,所以下方金属板的电压是0V,如果我们给上方金属板加一个大于0V的电压,由于两块金属板产生了电压差,因此电容里面的电荷会移动,也就是所谓的给电容充电,这个电容里面就可以保存一定的电荷。若给上方金属板加的是0V或者1V的电压,两块金属板不产生电压差或者产生的电压差很小,也就不会给电容充电,也就不会保存电荷。所以我们可以根据电容是否保存了电荷来对应二进制的0和1。

我们来看如何读出一个二进制:
我们来看如何写入一个二进制:
我们把多个存储元进行科学合理的连接,那么我们就可以一次性读出或者写入多个二进制数据,图中红线代表连接了每一个MOS管,我们只需给红线通电,那么所有的MOS管都可以连通,电容存储的电荷就可以顺着绿色的线流出,我们只需要看每条绿色的线有没有电流,就可以读出二进制是0还是1。
这样的一整行存储元就是我们的一个存储单元,也就是一个存储字,而多个存储单元则构成了存储体,也称为 存储矩阵,上图中存储体只包含了两个存储单元。我们一次可以读出一行的二进制就是存储字,如上图例子当中的存储字长为8bit,因为一行共有8个存储元。这样我们就理解了为什么每次存储器每一次读或者每一次写都是一个存储字。
1个字节B一定等于8比特bit,而一个存储字等于多少比特是看存储单元中一行是几个存储元。
如何根据地址来决定读写哪个存储字呢?这就涉及到译码器的使用,译码器的作用:n位二进制地址会对应2n个存储单元,所以译码器会根据地址寄存器MAR里面的地址,把它转变为某一条选通线的高电平信号。

例如图中CPU给MAR送来的地址是3个0,对应十进制的0,所以译码器会把第0根字选线给一个高电平的输出,这样第一个存储字所对应的存储元都会被选通,每个地址会对应译码器的一条输出线,总共有23=8个地址,因此译码器的输出端总共会有8条线。所以经过译码器的处理,一个地址信号会被转换为译码器的某一条输出线的高电平信号。
当图中第0条字选线被接通之后,我们就可以通过图中数据线(绿色的线)把每一位二进制信息传送到MDR当中,CPU会通过数据总线从MDR当中取走这一整个字的数据。数据总线的宽度=存储字长。
例子中的总容量 = 存储单元个数×存储字长,存储单元个数=23(即多少行),因为只有3位的地址。存储字长是8bit,所以总容量为23×8bit=8B

我们继续完善存储器芯片的构成,我们需要增加一个控制电路,用于控制译码器MDR和MAR,当CPU通过地址总线把地址送到MAR当中,但是由于我们使用的是电信号来传送这些二进制数据,而电信号难免会有不稳定的情况,因此在MAR里面的电信号稳定之前,这个地址信息是不能送到译码器当中的。这就是控制电路的作用,只有MAR稳定之后控制电路才会打开译码器的开关,让译码器来翻译这个地址,然后给出相应的输出信号。同样,在数据线输出时,只有当电信号稳定,电路才会认为此时的输出是正确的,所以控制电路也会控制MDR在稳定后给数据总线送出数据。
同时存储器芯片还需要向外提供片选线,通常表示为CS或者CE(上方加一条横线)来表示片选线的电信号,头上划线表示该信号低电平有效。CS表示Chip-select芯片选择信号,如果给出的芯片选择信号是一个低电平则说明这个芯片的总开关是被接通的。有些书也会命名为CE表示Chip-enable芯片使能信号。
另外,控制电路还要提供读控制线和写控制线。若分成两条线,WE表示允许写,上方划横线表示当写控制线的信号是低电平的时候表示此时这个芯片正在进行写操作,也就是把MDR里面的数据输入到各个存储元当中。OE表示允许读,上方划横线表示当读控制线的信号是低电平的时候表示此时这个芯片正在进行读操作,也就是读出存储元里面的数据。若分成一条线,则低电平表示芯片正在执行写操作,高电平表示芯片正在执行读操作。采用两种方案芯片对外暴露的金属引脚是不一样的。

整体来看,我们的存储芯片由存储矩阵(存储元),译码驱动包含译码器和驱动器,通常会在译码器后面接上一个驱动器,这个驱动器的作用保证译码器输出的电信号是稳定可靠的,驱动器其实就是将电信号进行放大的一个部件,读写电路包括控制电路和字选线等。另外一块存储芯片需要接收来自外界的地址信息,地址信息通常是由CPU通过地址总线传过来的,另外还需要数据线进行数据的传输。除此之外,还需要通过片选线的电信号来确定这块存储芯片此时是否可用,另外还需要提供读/写控制线,可能有两条,也可能有一条。
一个内存条可能包含多块存储芯片,每一块存储芯片都是1GB,8块就能存储8GB的数据。若我们要读取的数据是其中的某一块存储芯片,那么我们提供了读写地址之后,我们只能让这块存储芯片工作,其他存储芯片不能工作。这就是片选线的作用,若我们只想读取这块芯片指定地址的数据,那么我们就需要让这块地址的片选线信号有效,也就是给CS(上方划线)信号一个低电平,而其他芯片的CS信号都给高电平。
每一个存储芯片都会向外暴露一个个金属引脚,这些引脚是用来接收地址信号、片选信号、数据信号、读写信号用的,所以通常题目会给芯片的参数信息,让你判断这块芯片引脚的数目至少是多少,其实就是**判断地址线有多少根、数据线有多少根,片选线1根,读/写控制线1根或2根,然后加和就是对应的引脚。**另外,还会有供电引脚、接地引脚。

若一块存储芯片有n位地址,那意味着地址线需要有n条,这n位地址又会对应2n个存储单元。知道了存储单元的数量,就可以乘以存储字长来得到总容量。
还会碰到 8×8位的存储芯片:

在上图中一个小方格表示1B,也就是8bit。一整行表示的是一个存储字(存储单元),也就是这块存储体的存储字长是4B,假设总容量是1KB,也就是256个存储字,也就是共256行。现代计算机都是按字节编址的,也就是说每个字节会对应一个地址,这个存储体当中共有1K个字节,就会对应1K个地址。我们的地址线就应该有10根,因为210=1K,整个地址信号是从10个全0到10个全1,0~1023。
我们按字寻址,由于一个字占4个字节,因此会将4个连续的字节地址合并,看成一整个字(也就是一行为一个字)。当我们指明了要读的是第几个字的时候,我们只需要把字地址进行算术左移两位(相当于×4),这样就可以把字地址转换成与之对应的字节地址。
如上图(红字部分分别为0号字1号字2号字3号字),假如我们要访问1号字,我们将1算术左移两位变成100,这个二进制对应的十进制是4,这样我们就得到这个字的起始字节地址为4(如上图红圈)。


动态RAM用于主存,SRAM用于Cache。高频考点是DRAM和SRAM的对比。

我们在上方介绍的其实就是DRAM芯片,DRAM芯片被用于主存。

如上图,我们之前说过,我们给字选择线加高电压,就会使得MOS管接通,若同时我们给数据线也加高电压,那么电容的上金属板就会是高电压,而下金属板由于接地所以是0V电压,这样会产生电压差,电容会存储电荷,这样我们就完成了二进制1的写入。若我们给数据线一个低电压,那么电容两块金属板没有电压差,电容不会存储电荷,这样就完成了二进制0的写入。因此电容内存储了电荷表示二进制1,电容内未存储电荷表示二进制0。

如上图,左边是栅极电容,右边是双稳态触发器,双稳态触发器中包含6个MOS管,分别用M1、M2、M3、M4、M5、M6标注,因为这个触发器可以呈现出两种稳定的状态
对于栅极电容组成的存储元,数据线只有一根,而双稳态触发器需要两根数据线来读出0或者1。对于双稳态触发器来说,如果它里面存储的是二进制的1,那么我们给字选择线高电压,右边数据线BLX会输出一个低电平信号,而若里面存储的是二进制的0,那么左边数据线BL会输出一个低电平信号。所以我们根据左右两边哪条线输出了低电平信号,我们就可以判断这个触发器里面存储的是0还是1了。这就是读出触发器里面数据的原理。
若我们要写入触发器数据0,我们给左边数据线BL低电平、给右边数据线BLX高电平,这样就可以使得触发器A点低B点高,这个状态对应的就是二进制的0。

那么栅极电容和双稳态触发器有什么区别呢?
读写数据
栅极电容在读出数据的时候,电荷会通过MOS管使得数据线上产生电流,也就是电容放电,电容放电了之后就会使得电容由1变为0,因此当我们读出栅极电容的信息之后,这个电容里面的信息被我们破坏掉了,是破坏性读出。若要解决这个问题,我们读出后应该有 重写操作,也称为 再生。也就是需要给电容重新充电,让其又变为1
双稳态触发器在读出数据的时候,触发器的状态保持稳定。是 非破坏性读出,无需重写
成本
因为栅极电容需要重写,所以读写速度更慢;而双稳态触发器不需要重写,所以读写速度更快。


电容里面虽然可以存储电荷,但是这个电荷会慢慢流失,电容电荷只能维持2ms,2ms后即使不断电电容里面的电荷也会消失。所以我们2ms之内必须 刷新 一次,也就是给电容充电。
双稳态触发器只要不断电,触发器的状态就不会改变。要不怎么叫双稳态呢?就问你服不服?

如上图,我们先看左边存储器简单模型,当我们给了一位地址给译码器之后,译码器会将其转换成其中某一条选通线的高电平信号,若我们有n位地址给译码器,那么就意味着译码器会有2n根选通线。若有20位地址,那么选通线就有220,这么多线根本难以实现。
所以看右边,我们将存储单元从一维列排列改成二维行列排列,这样n位地址会被拆分成行地址和列地址,行地址译码器处理一半210根选通线,列地址译码器处理一半210根选通线,这才1024根线,好实现!

若我们给的地址是 00000000,8位的0。若按照左边的简单模型,8位地址译码器会有28根选通线,8位0转换为十进制0,那么译码器就选通0号线,也就是选中0号存储单元。若按照右边的模型,8位0会被分割为前4位0和后4位0,转换为十进制是0和0,也就是0行0列,行译码器有24根选通线,列译码器有24根选通线,这样就选中了(0,0)存储单元。
如何刷新?有硬件支持,读出一行的信息后重新写入,占用1个读/写周期(相当于进行一次写操作呗)
在什么时刻刷新?

假设DRAM内部有128行×128列的存储单元,并且每个读写周期是0.5us,那么电容可以坚持的最长的时间是 2ms/0.5us=4000个读写周期,读写周期也称为存取周期
思路一:每次读写完都刷新一行【分散刷新】
思路二:2ms内集中安排时间全部刷新【集中刷新】
思路三:2ms内每行刷新1次即可【异步刷新】

这里看一下最后一行送行列地址:

同时送的意思是行地址和列地址会同时丢给行地址译码器和列地址译码器,如果采用这种策略,我们地址有多少位我们就需要设置多少根的地址线。
行、列地址分两次送,可使地址线更少,从而芯片引脚也更少。原本需要n个引脚来接收n个地址,现在只需要n/2个引脚就可以接收n个地址,先接收行地址,再接收列地址。

MROM(Mask Read-Only Memory)——掩模式只读存储器
PROM(Programmable Read-Only Memory)——可编程只读存储器
EPROM(Erasable Programmable Read-Only Memory)——可擦除可编程只读存储器
Flash Memory ——闪速存储器(注:U盘、SD卡就是闪存)
SSD(Solid State Drives)——固态硬盘

计算机的主存是用来存放一系列的指令和数据的,CPU要做的事就是从主存中取一条条指令并执行指令,然而RAM是易失性的芯片,断电后RAM内数据全部丢失,也就是关机后主存的数据全部没了。当开机后,就需要将辅存内的操作系统重新调入到主存,但是由于此时的主存是空的,并没有指令,所以CPU会去主板上的ROM芯片来读取开机的指令,这个ROM芯片就是BIOS芯片,其中BIOS芯片存储了 自举装入程序,负责引导装入操作系统(开机)。ROM是非易失性的,即使计算机没有供电,ROM芯片里面的指令也不会丢失。
注意:虽然这块ROM芯片是集成在主板上,但是在逻辑上,我们将其看成是主存的一部分。逻辑上,主存由RAM+ROM组成,且二者常统一编址。也就是在计组这门课,我们说的主存,除了内存条外,还应该加上BIOS芯片。

RAM支持随机存取,很多ROM也支持随机存取,我们给一个地址,访问速度并不会因为地址而改变。


单块存储芯片需要对外暴露这样的接口:
思考:

现在的计算机CPU里面集成了MAR和MDR,MDR里面存储的数据也就是要读或者要写的数据是通过数据总线和主存进行交换,MAR里面存储的地址数据是通过地址总线送给主存。CPU通过控制总线向主存发送读写控制信息。现在的主存一般会包含很多块的存储芯片。如下图,用红框圈出来的黑黑的部分就是存储芯片,各有4块。

多块存储芯片如何与CPU进行交互呢?为了方便,我们给存储器芯片的输入信号和输出信号进行命名

上面有横线说明低电平有效,没有横线说明高电平有效。

如果我们有8K×1位的存储芯片,这个单块存储芯片如何和我们的CPU进行连接呢?首先8K也就是有8K=213的存储单元,所以我们需要用13根地址线来表示这8K个地址,因此这块芯片对外暴露的地址应该是A0 -> A12共13条地址线,CPU会把它想访问的地址通过地址总线(红色)送过来,这样就完成了地址线的连接。
WE信号表示Write Enable写使能信号,头上未划线表示高电平有效,也就是当信号是高电平信号时,CPU要向存储器里面写数据,而当信号是低电平信号时,表示CPU要从中读数据。CPU也会有一个引脚来发送读写控制信号,这个信号通过控制总线(褐色)传给芯片。
CPU可以通过数据总线(绿色)来读或者写8bit数据,然而由于存储芯片存储字长的限制,我们每次只能通过数据总线来传送一个bit,数据总线的利用率很不充分。
存储芯片还有一个CS片选信号,由于只有这一块芯片工作,所以我们可以简单粗暴的接上高电平信号。因为CS头上没有划横线,意味着片选信号高电平有效。
此时整个主存只有一个存储芯片,每次只能读或者写一位数据,因为主存的存储字长是1bit。

我们可以给主存再加上一块相同信号的存储芯片,CPU将想要访问的地址信息传给这个芯片,两个芯片都有8K的存储单元,所以如果把13位的地址信息同时送给这两个芯片,这个地址信息可以选中两个芯片相同位置的存储单元。当然我们也可以把CPU发送的读写控制信号WE同时送给两个芯片,使得两个芯片要么同时读,要么同时写。右边芯片读出的数据我们可以作为CPU读入的D1这一位的数据。接下来只需要个CS片选信号加一个高电平。这样这两块芯片就可以同时工作。
进行了这个改造之后,我们整个主存储器总共有两个存储芯片,存储字长扩展成了2位,现在我们就可以同时读或者同时写两位的信息。

同理继续增加芯片,每个芯片都有8K个存储单元,CPU发出的A0到A12这13位的地址信息会同时送给8片存储芯片。
我们把8片8K×1位的存储芯片通过位扩展扩展成1个8K×8位的存储器,总容量为8KB

我们有一片8K×8位的存储芯片,CPU可以同时读或者写8位的信息,由于我们的存储芯片存储字长已经有8位了,CPU的处理数据的宽度和存储字长是可以对应的,对于这样的场景,我们不需要进行位扩展,因为数据总线的利用率已经达到100%。
8K也就是有8K=213的存储单元,所以我们需要用13根地址线来表示这8K个地址,因此这块芯片对外暴露的地址应该是A0 -> A12共13条地址线,CPU会把它想访问的地址通过地址总线(红色)送过来,这样就完成了地址线的连接。
存储芯片还有一个CS片选信号,由于只有这一块芯片工作,所以我们可以简单粗暴的接上高电平信号。因为CS头上没有划横线,意味着片选信号高电平有效。

注意:CPU还有三个地址A13->A15没有被用到,也就是CPU的MAR有16位,我们现在只利用了13位,我们如何解决这个问题呢?
我们会买一个同型号的芯片,按照位扩展,将低位的13根地址线(红线)也连接在第二块芯片上,同时8位数据通过地址总线(绿线)传给CPU,读写控制线(褐线)也连接。当CPU给出一个全0的地址之后,地址会通过地址总线同时传给左右两个芯片,两个芯片的8位数据都会通过数据总线传给CPU,这样就会发生数据冲突。

所以我们进行改造,我们将A13地址信息连到左边芯片,将A14地址信息连到右边芯片,由于片选信号高位有效,因此当A13和A14分别为0的时候,意味着左边芯片片选信号有效开始工作,而右边芯片片选信号无效不工作。这样虽然不会发生数据冲突,然而当A13和A14都为1时,还是会发生数据冲突。
上述连线方法称为线选法:我们会用专门的地址线作为片选信号来选中其中的某一块芯片,如果CPU有n条地址线,采用这种方法我们就只能有n个片选信号。

如上图,我们对线选法进行改进,将地址A13连接的地址总线分成两条线接出去,上方线接了非门取反电路,当A13地址线的信号是1,那么下方线直接连接到左边芯片,左边芯片进行工作。而上方线经过非门取反的改造信号就会变成0,这样右边芯片不会工作。
非门取反电路我们可称为
1-2译码器:输入一位的地址信息,可能呈现21=2中不同的状态,这两种状态会被译码器翻译为高电平、低电平。译码器片选法:我们可以译码器来处理CPU的高位地址部分,如果CPU给出n位高位地址,也就是n条线,那么会有2n个片选信号。

如上图,我们给3位地址线,经过译码器翻译会产生23共8个片选信号,所以也称为3-8译码器。
有了译码器之后,我们再来看如何更好的进行 字扩展

如上图,2-4译码器,输入两个信号输出四个信号。4个芯片,每个芯片都是接收由CPU发送的低地址13位的地址信息(为了画图好看,不要理解为第二块芯片的地址信息是由第一块传来的),图中片选线CS上方加了横线,表示低电平有效,我们通过表示低电平有效时会在译码器输出线末尾画一个小圆,同时译码器的输出端也会画一个小圆圈(非门的图示最重要的就是小圆圈),译码器输出端小圆圈我们可以理解为进行了一次取反
00....0 -> 001....1 也就是00后面跟上13位,总共8K个地址01....0 -> 011....1 也就是01后面跟上13位,总共8K个地址
考试为了折磨人,可能会给A13和A15作为译码器的输入信号,这样无论A14是取0还是1都影响不到选片操作。

线选法:将n条多余的地址线将它们作为n个片选信号,电路简单,地址空间不连续
译码器片选法:将n条多余的地址线将它们作为2n个片选信号,电路复杂,地址空间连续

8块芯片,其中每两块芯片为一组,实现了位扩展,因为每块芯片是16K×4位,而CPU可以同时读写8位数据,所以我们可以让两块芯片为一组。每块芯片的字数是16K=214,所以将CPU的A0到A13这14位的地址信息作为片内地址,而高位地址A14和A15我们可以接上2-4译码器。总共有4个片选信号,所以我们我们可以接上4组,而每一组的存储芯片总共有16K个存储单元,每个存储单元可以存8位的数据。4组结合我们就得到64K×8位的存储器。


如上图,3-8译码器,输入3个地址信号,会输出8个片选信号,其中只有一条输出高电平,其余均输出低电平。这个译码器就可以和高电平有效的存储芯片配合使用,
译码器输出端画了小圆圈,表示输出的有效信号是0,其他的无效信号都是1,这个译码器就可以和低电平有效的存储芯片配合使用。
译码器还可能有使能(使译码器能够工作)接口,没有画小圆意味着这个译码器高电平有效,也就是译码器在高电平就可以工作。

还有可能有多个使能接口,下方两个使能接口画了小圆,表示下方两个使能信号必须是低电平,上方的使能信号必须是高电平,只有这样译码器才能开始工作。
如上图左边译码器可以工作,地址信号101转换成十进制是5,那么编号为5的输出端会输出0,其余输出端会输出1。右边译码器若使能信号是000,那么译码器无效,此时所有输出端都会输出1。同样若使能信号是非法状态,那么译码器均无效,输出端都输出1。利用这个特性:CPU可以使用译码器的使能端控制片选信号的生效时间。

CPU中还会有一个MREQ主存储器请求的一个信号,当CPU想要访问主存的时候,就会使得MREQ有效。当CPU没有发出主存请求信号的时候,这个G2B输出的就是一个1,也就是译码器不工作,所有的译码器输出端都输出1。只有译码器接收到0信号之后,译码器开始工作,才会将地址信号映射为选通信号。
CPU首先通过地址线送出地址信号,包括低地址13位A0-A12,还有高地址3位A13-A15。等信号发出稳定后,CPU再发出主存请求信号,也就是让译码器某一个选通线有效,这样就可以保证当一块存储芯片被选通时,这块存储芯片所接收到的地址信号一定是稳定的。这就是译码器使能端的作用。

回顾一下存取周期:从主存中可以连续读/写的最短时间间隔,我们之前说过,DRAM芯片的恢复时间比较长,有可能是存取时间的几倍,SRAM的恢复时间比较短,即使存取时间很快, CPU又必须等这么一段恢复时间才可以读/写下一个存储字。
这tm的就引出来了好多问题:我们通常都是双核、四核CPU,是不是意味着第一个CPU经历存取时间,第二个CPU必须等待恢复时间才可以进行下一次读写?即使你的电脑太拉是单核CPU,要知道CPU的读写速度可是比主存快很多,主存要是恢复时间太长咋办?提升主存速度呗!

双端口RAM作用:优化多核CPU访问一根内存条的速度

例如上图,双核CPU,只有一根内存条(中间),内存条采用双端口RAM技术,这样两个CPU就可以通过两个端口对内存条进行并行的访问,若要支持双端口RAM技术:需要有两组完全独立的数据线、地址线、控制线。CPU、RAM中也要有更复杂的控制电路。
两个端口对同一主存操作有以下4种情况:
解决办法:置"忙"信号为0,由判断逻辑决定暂时关闭一个端口(即被延时),例如让CPU2连接的端口关闭,未被关闭的端口正常访问,被关闭的端口延长一个很短的时间段后再访问。
多体并行存储器可以解决单核CPU等待主存恢复时间过长的问题。
CPU在对内存进行访问时会提供内存地址,采用高位交叉编址的意思是我们会采用内存地址更高的比特位来区分我们想要访问的是哪一个存储体(下图可以理解为内存条),采用高位交叉编址的意思是我们会采用内存地址更低的比特位来区分我们想要访问的是哪一个存储体。

如上图,每个存储体有8个存储单元,左图共有 4×8 = 32 = 25 个存储单元,我们可以用 5bit 来作为我们想要访问的主存地址,如果我们采用高位交叉编址的话,第一个存储体的第一个存储单元我们给的地址是00,高位的两个00表示这是M0这个存储体,后面的三个0表示在这个存储体内部的第几个存储单元。我们将高位两个0称为体号,后面的三个0称为 体内地址。下一个地址是00001,依次类推。低位交叉编址也类似,对于同一个存储体的存储单元来说体号都是相同的,只是体内地址不同。我们将地址信息翻译成十进制,对于高位交叉编址如上图分别是0、1、2、3、4…竖向递增,而对于低位交叉编址是横向递增。

我们假设每个存储体的存取周期为T,存取时间为r,假设T=4r,也就是存取时间为r,恢复时间为3r。
连续访问主存地址,低位交叉编址方案比高位交叉编址方案效率高
那对于低交叉编址方案,我们每次取多少个体呢?
对于上述采用"流水线"的方式并行存取(宏观上并行,微观上串行),当存取周期为T,存取时间为r,为了使得流水线不间断,应保证存储体数 m ≥ T/r ,对于上述例子 4 = 4r/r。还有一种描述方式,当存取周期为T,总线传输周期为r,为了使得流水线不间断,应保证存储体数 m ≥ T/r。 存取时间 = 总线传输周期可以看作相同☺
思考:当给一个地址x,如何确定它属于第几个存储体呢?
多体并行存储器:

单体多字存储器:相当于对多体并行存储器进行了合并,如上图,每一列方格对应一个多体并行存储器,单体多字存储器共用一套读写控制电路、地址寄存器和数据寄存器。
这样每个存储单元存储m(多体并行存储器的个数)个字,总线宽度也是m个字,一次并行读出m个字。每次只能同时取m个字,不能单独取其中某个字。所以适合用来读取指令和数据在主存内是连续存放的


计算机的外存储器又称为辅助存储器(辅存),目前主要使用磁表面存储器。所谓"磁表面存储器",是指把某些磁性材料薄薄地涂在金属铝或塑料表面上作为载磁体来存储信息。磁盘存储器、磁带存储器和磁鼓存储器均属于磁表面存储器。
磁表面存储器的优点:
磁表面存储器的缺点:

如上图是硬盘拆开后的样子,驱动轴会使得磁盘旋转,在驱动轴上有很多盘片,每个盘片的表层会涂上磁性材质,磁性材质是一圈一圈的涂在上面,当我们想要读取某一圈磁性材质里面的数据时,磁头移动臂就会移动到对应的那一圈,磁头臂上有读/写磁头,可以用来读/写二进制。
原理:当磁头和磁性记录介质有相对运动时,通过电磁转换完成读/写操作。
编码方法:按某种规律,把一连串的二进制信息变换成存储介质磁层中一个磁化翻转状态的序列,并使读/写控制电路容易、可靠地实现转换。
磁记录方式:通常采用调频制(FM)和改进型调频制(MFM)的记录方式。
外存储器既可以作为输入设备,也可以作为输出设备。(既可以存数据,也可以读数据)

磁盘设备的组成:
存储区域
一块硬盘含有若干个记录面(盘片,上图有4个盘片),每个记录面划分为若干条磁道(上图一圈磁性材料我们称为一条磁道),而每条磁道又划分为若干个扇区,扇区(也称块)是磁盘读写的最小单位,也就是说磁盘按块存取。
硬盘存储器
硬盘存储器包括磁盘驱动器、磁盘控制器和盘片组成。
磁盘的容量:一个磁盘所能存储的字节总数称为磁盘容量。磁盘容量有非格式化容量和格式化容量之分。
记录密度:记录密度是指盘片单位面积上记录的二进制的信息量,通常以道密度、位密度和面密度表示


主机向磁盘控制器发送寻址信息,磁盘的地址一般如图所示:

哪个硬盘的哪个盘面的哪个磁道的哪个扇区
磁盘的主要操作时寻址、读盘、写盘。每个操作都对应一个控制字,硬盘工作时,第一步是取控制字,第二步是执行控制字。硬盘属于机械式部件,其读写操作时串行的,不可能在同一时刻既读又写,也不可能在同一时刻读两组数据或写两组数据。
RAID廉价冗余磁盘阵列是将多个独立的物理磁盘组成一个独立的逻辑盘,数据在多个物理盘上分割交叉存储、并行访问,具有更好的存储性能、可靠性和安全性。

RAID0方案的思想如上图,有两个磁盘,A1、A2、A3、A4是在逻辑上相邻的数据,逻辑上相邻的数据存放在不同磁盘的扇区中,几个磁盘交叉并行读写,不仅扩大了存储容量,而且提高了磁盘数据存取速度,但RAID0没有容错能力。
RAID1方案的思想如上图,镜像磁盘阵列,使得两个磁盘同时存一份数据,两个磁盘互为备份。如果一个磁盘出现故障,我们还可以从另一个磁盘中读出数据。但是这意味着容量减少一半。
越向后的方案可靠性越高


如上图,系统通过I/O总线向SSD发送要读或写的逻辑块号,也就是指明了一个逻辑地址,然后这个逻辑地址经由闪存翻译层的翻译之后,将逻辑块号映射到对应的物理地址。所以闪存翻译层做的就是一个地址变换的工作,一个固态硬盘里面可能会包含多个闪存芯片,这些闪存芯片里面都可以存储数据。每一块闪存芯片,它里面会有若干个数据块组成,比如说每个块的大小是16KB ~ 512KB,而每一个块又可以进一步被拆解为一个一个的页,每个页的大小可能为512B ~ 4KB。
需要注意的是系统对固态硬盘的读写是以页为单位的,每次读或者写一个页,系统通过I/O总线指明要读/写的逻辑块号是多少,如果数据是存放在磁盘(机械硬盘)里面,那么一个逻辑块对应的就是磁盘的块,或者是磁盘的一个扇区,磁盘的读写单位就是以块为单位。但是如果系统此次要读的逻辑块是存放在固态硬盘里面,那么这里的逻辑块就对应固态硬盘的一个页,而不是对应固态硬盘的一个块。固态硬盘的读写单位是以页为单位的,因此固态硬盘的一个页相当于磁盘的一个扇区,固态硬盘的一个块相当于磁盘的一个磁道,一个磁道里面会包含多个扇区。

如上图,整个微信在内存中占1GB的运行内存,我们把微信当中的一小部分代码放到Cache当中,假设CPU对Cache的一次访问所需时间为tc, CPU对内存的一次访问所需时间为tm,如果CPU想要执行的是视频聊天相关的代码,那么这部分代码可以在Cache里面找到,如果CPU想要访问的数据在Cache里面可以直接找到,我们称这种现象为Cache命中,与之对应的指标是
只要知道访问Cache所需要的时间和访问主存所需要的时间以及Cache的命中率,我们就可以算出 Cache-主存 系统的平均访问时间:
t
=
H
t
c
+
(
1
−
H
)
(
t
c
+
t
m
)
平均访问时间
=
C
a
c
h
e
命中率
×
访问一次
C
a
c
h
e
时间
+
未命中率
×
(
访问一次主存时间
+
访问一次
C
a
c
h
e
时间
)
t
c
<
t
<
t
m
t = Ht_c+(1-H)(t_c+t_m) \\ 平均访问时间 = Cache命中率×访问一次Cache时间+未命中率×(访问一次主存时间+访问一次Cache时间) \\ t_c < t
上述公式的含义是CPU是先去Cache里面找,无论找得到找不到都需要花tc的时间,若没有命中,则CPU会去主存里面找数据,共花费tc+tm的时间,这种情况发生的概率为1-H。
当CPU同时去Cache和主存里面找数据,当经过tc时间,在Cache里面命中,则立即停止在内存中的查找,所以在命中的情况下CPU的访问时间同样只需要tc的时间,这种情况发生的概率为H。若在Cache里面没有命中,啧经过tmCPU就可以在内存当中找到数据,而这种情况发生的概率为1-H。
t
=
H
t
c
+
(
1
−
H
)
t
m
t=Ht_c+(1-H)t_m
t=Htc+(1−H)tm
假设Cache的速度是主存的5倍,且Cache的命中率为95%,则采用Cache后存储器性能提高多少(设Cache和主存同时被访问,且Cache命中则中断访问主存)?
设Cache的存取周期为t,则主存的存取周期为5t
若Cache和主存同时访问,命中时访问时间为t,未命中时访问时间为5t
平均访问时间为
0.95
×
t
+
0.05
×
5
t
=
1.2
t
性能为原来的
5
t
1.2
t
=
4.17
倍
平均访问时间为0.95×t + 0.05×5t = 1.2t \\ 性能为原来的 \frac{5t}{1.2t} = 4.17倍
平均访问时间为0.95×t+0.05×5t=1.2t性能为原来的1.2t5t=4.17倍
若先访问Cache再访问主存,命中时访问时间为t,未命中时访问时间为t+5t
平均访问时间为
T
a
=
0.95
×
t
+
0.05
×
6
t
=
1.25
t
性能为原来的
5
t
1.25
t
=
4
倍
平均访问时间为T_a=0.95×t + 0.05×6t = 1.25t \\ 性能为原来的 \frac{5t}{1.25t} = 4倍
平均访问时间为Ta=0.95×t+0.05×6t=1.25t性能为原来的1.25t5t=4倍
基于局部性原理,不难想到,可以把CPU目前访问的地址“周围”的部分数据放到Cache中。如何界定“周围”?
将主存的存储空间“分块”,如:每1KB为一块。主存与Cache之间以“块”为单位进行数据交换

如上图假设整个主存总共是4MB,4M = 222,它以1KB均分,1K = 210,所以整个主存被分为了 222/210=212=4096块,我们可以给主存的各个块进行编号,从0 ~ 4095。如果这个主存是按字节编址,那么就意味着这个主存它的地址包含22个比特位(因为222=4M),我们可以将它表示为两个部分,前面的12位表示块号,因为12位刚好可以表示0~4095,后面的10位我们可以用来表示块内地址,因为每个块有1K个存储单元,刚好可以用10个比特为来表示。
假设Cache的存储空间只有8KB,只能被分为8个块,块号分别为0~7。这样我们对Cache和主存之间的数据交换就可以用一块一块来交换。
注意:操作系统中,通常将主存中的“一个块”也称为“一个页/页面/页框”,Cache中的“块”也称为“行”
CPU优先从Cache里面找数据,如果在Cache里面找不到CPU就会去主存里面找数据,当CPU访问了主存的某一个存储块之后,一定会把这个主存块调入cache(复制一份)。


如何区分Cache中存放的是哪个主存块?给每个Cache块增加一个“标记”,记录对应的主存块号。如上图0号Ca che块对应的是9号主存的副本,1号Cache块对应的是8号主存的副本,2号Cache块对应的是5号主存的副本,我们没有记录后续Cache块的标记,我们让它保持为空,但是对于计算机硬件来说,所有的二进制位只会出现0/1的状态,不可能为空,刚开始标记都会被置为全0,那么后面的几个Cache块难道就是0号主存的副本吗,显然不对。所以我们还需要增加一个有效位,有效位为1表示这个标记是有效的,有效位为0表示这个标记是无效的
全相联映射:主存块可以放在Cache的任意位置

假设某个计算机的主存地址空间为256MB,按字节编址,其数据Cache有8个Cache行(即Cache块,与主存块的大小相等),行长为64B
如上图有8个Cache块,每个块有64b,总共512b。再来看主存主存也是被分为一个一个的块,256M = 228,所以主存地址共28位,228/64=222,这意味着整个主存会被划分为222个主存块,我们用前22位作为主存块号,用后6位作为块内地址,主存块号是0~222-1。每个主存块是64B,也就是每个主存块有64个存储单元,对应64个地址,我们可以标注每个主存块的地址范围。
开始所有的有效位全部置为0,我们假设要把主存的第0块放入Cache 当中,由于采用的是全相联映射,这就意味着这个主存块可以放在Cache的任何一个位置,我们挑选3这个位置,为了区分3这个地址存放的是哪个主存块,因此我们需要做一个标记,标记的就是主存块的块号(22个0)。假设我们将图中主存中的紫色部分放入Cache中的1号位置,同样我们需要将有效位改为1并且标记。
采用这种方式CPU如何访问主存的地址呢?假设CPU要访问的主存地址为1…1101(图中紫色块的块号)001110
直接映射方式:每个主存块只能放到一个特定的位置:Cache块号=主存块号% Cache总块数

如上图将0号主存块放到Cache中,0%8=0,这就意味着0号主存块只能放到0号Cache块当中,我们将有效位改为1并且进行标记,接着将8号主存块放入Cache中,8%8=0,这就意味着8号主存块只能放到0号Cache块当中,所以我们只能把之前存放的数据给覆盖掉,并且将标记也改为8号主存块的主存块号。
我们看能否优化标记,看直接映射的规则,主存块号%23,相当于留下最后三位二进制数,这意味着计算机硬件并不需要进行取余操作,还是将主存块号末尾三位(上图中橙色部分)截取掉,这三个二进制位就指明了主存块应该存放在Cache块的什么位置。所以说只要能存放在0号Cache块,那么它的主存块号的最后三位一定都是0。
当Cache的总块数=2n,则主存块号末尾的n位直接反映它在Cache中的位置,因此我们的标记就不需要存主存块号的最后三位,如上图标记位只需存储19位。
采用这种方式CPU如何访问主存的地址呢?假设CPU要访问的主存地址为0…01000(图中橙色块的块号)001110
组相联映射:Cache块分为若干组,每个主存块可放到特定分组中的任意一个位置,所属分组=主存块号%分组数

如上图将1号主存块放到Cache中,1%4=1,这就意味着1号主存块只能放到第1组Cache块当中,我们将有效位改为1并且进行标记,接着将图中222-3号主存块放入Cache中,222-3%4=1,这就意味着这个主存块只能放到1号Cache块当中,将有效位改为1并且进行标记。
我们看能否优化标记,看组相连映射的规则,主存块号%22,相当于留下最后两位二进制数,这意味着计算机硬件并不需要进行取余操作,还是将主存块号末尾两位(上图中橙色部分)截取掉,这两个二进制位就指明了主存块应该存放在Cache块的什么位置。所以说只要能存放在1号Cache块,那么它的主存块号的最后两位一定是01。
当Cache的总块数=2n,则主存块号末尾的n位直接反映它在Cache中的位置,因此我们的标记就不需要存主存块号的最后三位,如上图标记位只需存储20位。
采用这种方式CPU如何访问主存的地址呢?假设CPU要访问的主存地址为1…1101(图中橙色块的块号)001110

Cache很小,主存很大。如果Cache满了怎么办? ——替换算法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7aDzzoki-1659271152694)(第三章存储系统.assets/76.png)]
所以直接映射是不需要考虑替换算法的
随机算法(RAND):若Cache已满,则随机选择一块替换。

设总共有4个Cache块,初始整个Cache为空。采用全相联映射,依次访问主存块{1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5}
随机算法——实现简单,但完全没考虑局部性原理,命中率低,实际效果很不稳定
先进先出算法(FIFO):若Cache已满,则替换最先被调入Cache的块

抖动现象:频繁的换入换出现象(刚被替换的块很快又被调入),先进先出算法——实现简单,FIFO依然没考虑局部性原理,最先被调入Cache的块也有可能是被频繁访问的
近期最少使用算法(LRU):为每一个Cache块设置一个“计数器”,用于记录每个Cache块已经有多久没被访问了。当Cache满后替换“计数器”最大的

我们接下来来看硬件是如何实现这个算法的:我们除了要给Cache块增加标记、有效位,还要增加计数器
LRU算法——基于“局部性原理”,近期被访问过的主存块,在不久的将来也很有可能被再次访问,因此淘汰最久没被访问过的块是合理的。LRU算法的实际运行效果优秀,Cache命中率高。若被频繁访问的主存块数量> Cache行的数量,则有可能发生“抖动”
最不经常使用算法(LFU):为每一个Cache块设置一个“计数器”,用于记录每个Cache块被访问过几次。当Cache满后替换“计数器”最小的

LFU算法——曾经被经常访问的主存块在未来不一定会用到(如:微信视频聊天相关的块),并没有很好地遵循局部性原理,因此实际运行效果不如LRU

CPU修改了Cache中的数据副本,如何确保主存中数据母本的一致性? ——Cache写策略

写命中:当CPU对Cache写命中时,只修改Cache的内容,而不立即写入主存,只有当此块被换出时才写回主存

CPU要对某一个地址进行写,并且这个地址所对应的主存块已经被调入Cache中,发生了命中的情况。在这种情况下,我们有两种处理的策略:第1种策略叫做写回法,第2种策略叫全写法
如上图,假设0号主存块已经被调入Cache3当中,紫色的主存块也被调入Cache中,假设此时CPU要写的地址恰好处于绿色的主存块的地址范围,如果采用写回法意味着CPU只会往Cache中的绿色副本中写入相应的数据,这个时候主存的数据母本和Cache中的数据副本就不一致,我们只有在Cache3块被替换的时候,我们才会把Cache3块的所有数据写回主存当中,而对于绿色的Cache1块,如果整个过程都没有被修改过的话,那么当Cache1块被替换的时候不需要写回主存块。
为了区分哪一个Cache块被修改过,我们还要增加一个字段脏位,当这个Cache块被修改后,我们需要将对应的脏位修改为1。
写回法减少了访存次数,但存在数据不一致的隐患。
我们来看第2种策略全写法:当CPU对Cache写命中时,必须把数据同时写入Cache和主存,一般使用写缓冲

如上图,假设0号主存块已经被调入Cache3当中,紫色的主存块也被调入Cache中,假设此时CPU要写的地址恰好处于绿色的主存块的地址范围,如果采用全写法意味着CPU会往Cache中的绿色副本和主存中的绿色母本中都写入相应的数据,这个时候主存的数据母本和Cache中的数据副本就一致,当Cache3块被替换的时候,我们不需要把Cache3块的所有数据写回主存当中,因为Cache中和主存中的数据是一致的
全写法:访存次数增加,速度变慢,但更能保证数据一致性。访存次数增加会导致写操作的速度变慢,为了减小CPU访存的次数,我们可以增加一个写缓冲

写缓冲是用SRAM实现的速度比较快,我们可以将其看成是一个先进先出的队列,当CPU对某一个地址进行写操作,并且这个操作命中的时候(即CPU在Cache找到了数据),我们首先会向Cache中写入数据,同时CPU也会向写缓冲里写入数据,接下来CPU又向紫色Cache块中写入数据,同样CPU也会将写的内容写入写缓冲里, CPU对写缓冲的写操作要比直接对主存写操作快得多。在CPU干其他事情的期间,会有专门的控制电路控制写缓冲的数据逐一写回主存。同样当我们使用这种方式,当Cahce块被替换的时候,我们也不需要写回主存。
使用写缓冲,CPU写的速度很快,若写操作不频繁,则效果很好。若写操作很频繁,可能会因为写缓冲饱和而发生阻塞

写分配法(write-allocate)——当CPU对Cache写不命中时,把主存中的块调入Cache,在Cache中修改。通常搭配写回法使用
写回法(write-back) —— 当CPU对Cache写命中时,只修改Cache的内容,而不立即写入主存,只有当此块被换出时才写回主存
例如:当没有命中的时候,CPU将主存中的块掉入Cache中,然后CPU对Cache中的副本进行修改,当这个Cache块被替换的时候才写回主存。

非写分配法——当CPU对Cache写不命中时只写入主存,不调入Cache,搭配全写法使用。
例如:当CPU对Cache中写不命中时, CPU会直接在主存中写而不调入Cache,只有CPU对地址进行读操作未命中时才调入Cache中,如果是写操作未命中则直接在主存中写而不调入Cache。
现代计算机常采用多级Cache,离CPU越近的速度越快,容量越小;离CPU越远的速度越慢,容量越大。各级Cache之间常采用“全写法+非写分配法”
