Skip to content

第一章:计算机的基本结构

计算机的基础组成部件

  1. 控制器
  2. 算数逻辑部件
  3. 存储器
  4. 输出设备
  5. 输入设备
计算机中小数的表示:

在计算机中小数有两种表现形式——定点数和浮点数

定点数:小数点的位置固定,以二进制为例,小数点前面每一位乘以2的正数次幂表示整数部分,后面的位乘以2的负数次幂表示小数部分(实际上整数就是小数点固定在最左端的定点数)。

浮点数:小数点的位置并不固定,通过“科学计数法”的方式来改变小数点的位置,例如C语言中的float类型数据占32位,其中包含1位标志位,23位有效位,8位表示有符号的指数大小。以二进制浮点数举例,如果有效位的值为A,8位指数位的大小为B,那么这个浮点数的大小为+/-A*2EB,注意有效位的小数点始终在第一个不为0的位之后。

公式如下: $$ (−1) s ×1.m×2 e $$ 为什么要使用浮点数呢?实际上使用相同的内存空间,浮点数能够表示的数值范围要比定点数大,为了满足更高的精度要求计算机中通常使用浮点数表示小数。32位浮点数表示的数的范围表示的范围大约在+/-10E+/-38之间,而定点数能表示的最小小数仅有10E-10

并行:

计算机的性能由其单位时间内处理任务的数量体现,而提升其性能的一个方法就是让处理器同时执行更多的任务,这就是所谓的并行(并行字面解释也是同时发生的意思),计算机一般通过下面三个方面提升其并行性:

  1. 指令级并行:

    处理器在执行一条指令通常需要分为多个步骤,单元A执行完命令1的步骤1后为了节约时间,不必等待命令1的后续步骤执行完成,而是直接执行执行命令2的步骤1,这样就取消了等待时间使每个处理单元都不会有空闲时间,没错,提升效率的本质就是狠狠压榨处理器😤

  2. 多核处理器:

    我们知道一个核心在一个微观时刻下只能执行一个线程,那么如果一个处理器有多个核心就可以同时处理多个线程了,这就是多核心处理器。

  3. 多处理器:

    和多核处理器一个思路,通过在一个计算机系统中添加多个处理器来提升并行性。

第二章:指令集系统结构

存储单元和寻址方式

计算机按照''字'来组织存储单元,这是由处理器能够处理的基本数据单位的大小决定的,例如32位计算机的数据总线的位数为32位,它一次就能传输32位的数据,因此它的字长也是32,同理64位计算机的字长为64。 除了数据总线外计算机还有地址总线,它是用来寻找内存单元的位置的,但是为每一位内存分配地址太过浪费,而按字分配不同的处理器字长并不同一,所以一般按照八位(1字节)分配地址,这就按字节寻址,而地址总线上的数据表示的地址就是字节的编号,32位处理器最大的寻址空间就是4GB,这是由地址总线的位数决定的。 在32位处理器中一个字中包含四个字节而大端小端则指的是字节在字中的两种排列方式。字中最左边存放数据的最高有效位,右边为最低有效位,按照大端的排列方式低地址的字节存放数据的高有效位,而高地址的字节存放地有效位,小端排列方式则正好相反,如下图所示。

大端小端

字的对齐就是指一个字的开始位置是字中所包含的字节数的整数倍,字对齐能够方便处理器访问存储单元,加速数据的读取速度。

常见处理器寄存器:

处理器在工作中,通过自身的寄存器与存储器中的数据和指令交互,进行数据处理。不同的寄存器有着不同的作用,下面是一些重要寄存器的介绍。

  1. 通用寄存器
    • 存储数据和中间结果,可用于算术和逻辑操作。通常有多个通用寄存器,如 R0R31(在某些架构中可能更多或更少)。
  2. 程序计数器(PC,Program Counter)
    • 指向下一条将要执行的指令的地址。执行指令后,PC会自动更新。
  3. 指令寄存器(IR,Instruction Register)
    • 存储当前正在执行的指令。
  4. 堆栈指针(SP,Stack Pointer)
    • 指向当前堆栈的位置,堆栈用于存储临时数据,比如函数调用的返回地址和局部变量。
  5. 基址寄存器和索引寄存器
    • 用于内存寻址。在某些指令集中,这些寄存器帮助计算内存地址。
  6. 状态寄存器(或程序状态字寄存器,PSW)
    • 包含处理器的当前状态,包括条件码(如零标志、进位标志等),指示运算的结果状态。
  7. 链接寄存器(LR,Link Register)
    • 当发生函数调用,中断时存放当前程序执行的位置,即保存PC寄存器的值。
RISC和CISC

处理器执行命令是通过一条条二进制指令进行的,处理器所使用的所有指令称为指令集,然而不同架构的处理器的使用的指令集也不同。X86架构的处理器使用复杂指令集,搭载这种处理器的计算机称为复杂指令集计算机(CISC),ARM架构的处理器使用精简指令集,搭载这种处理器的计算机称为精简指令集计算机(RISC)。下面是两种指令集的的特点

  • RISC:
    1. 每条指令为一个字长,指令简单运行效率高。
    2. 使用load/store体系结构,只能通过Load和Store命令操作存储器操作数(存储在主存中的数据),算数或者逻辑运算中的所有操作数必须是寄存器操作数(存储在处理器寄存器中的数据)或者是明确指出的数据。
  • CISC:
    1. 每条指令的长度不固定,但是可以实现复杂的操作。
    2. 寻址方式复杂多变,能够有效地操作复杂数据结构。

RISC精简了硬件设计,使处理器高效运行,但是同时为实现目的需要调用更多的指令,造成软件程序更为庞大;CISC仅需较少的指令就能实现目标功能,但是由于指令集的复杂它的一个指令往往需要处理器执行更多的步骤才能完成。因此两种指令集各有优缺点(要么软件换硬件,要么硬件换软件

常见汇编命令:

介绍一下精简指令集的常见汇编指令:

assembly
ADD	R4,R2,R3 		#将R2和R3寄存器中的值相加存储在R4中
ADD R4,R2,#400 		#将R2中的值加上400存放在R4中,#400称为立即执行数
ADDI R4,R2,400 		#和上式效果相同
Load R2,A  		    #将存储单元A中的内容存放在R2中
Store R4,B		    #将R4寄存器中的内容存放在内存单元B中
Call C			   #调用C位置的子程序
子程序调用与堆栈:

二进制程序由一条条处理器指令组成,处理器在执行程序时按照顺序一条条的执行指令,PC寄存器中存放着下一条指令存放的地址,当一个模块需要在不同位置多次调用时,通常会把他单独存放在一个位置(就像C语言中的函数一样)称这样的模块为子程序,当需要使用子程序时就让PC寄存器保存下当前的值,然后跳转到子程序的位置。当子程序执行完毕,就会根据PC寄存器之前保存的值跳转回到程序原来的位置继续执行下面的指令。 上面这个过程还存在需要讨论的细节,就是PC的值保存在什么地方。在发生一次子程序调用时,PC寄存器的值可以保存在LD寄存器中,当子程序执行完之后调转到LD寄存器的位置继续执行即可。但是如果发生镶嵌调用,如果还用这样的方法第一次调用的返回值就会被覆盖。 因此需要采用栈存储的方式,当子程序发生调用时将它的返回地址和调用参数存放在栈中,当调用结束后将存放的数据出栈就即可,由于栈后进先出的特点可以在将镶嵌调用的地址和参数据存放在栈中,它会按照顺序依次返回。 在程序执行的过程中栈中存放的就是程序的局部变量,子程序的参数,返回值和保存的返回地址。为了操作栈空间我们需要一个SP寄存器专门用于存放指向栈顶位置的指针,还有FP寄存器(结构体指针,这一般由一个通用寄存器存储)访问子程序的参数和局部变量。SP始终指向栈顶,FP指针指向的位置向下偏移为子程序参数,向上偏移一次保存着局部变量和寄存器的保存值。

SP和FP指针

第三章/第七章:输入/输出结构

总线结构:

计算机内部的设备诸如处理器,IO设备(键盘,鼠标等),存储器等通过总线连接起来,总线一般由三种线组成:

  • 地址总线:

    前面提到过存储器单元都有自己的地址编号,对于IO设备的寄存器也有相应的地址编号,如果将两者的地址编号排列在一起,那么计算机中所有的数据单元都位于同一个地址空间(这样的说法可能有些抽象,但实际上就是所有的内存单元都有一个独一无二的编号,而这些地址编号按顺序排列就组成了地址空间),这样处理器只需要访问的数据的地址,放在数据总线上,总线上的设备的地址译码器会根据地址总线上的数据来判断处理器是否要访问自己,进而响应控制总线和数据总线。

    地址总线的位宽(就是一次传输数据的位数)决定了处理器的最大寻址空间,一般计算机的地址总线的位宽为32或者64,比如32位的地址总线最大的寻址空间为2的32次方也就是4GB的空间大小。这意味着处理器主存大小最大为4GB(注意不是内存的大小,因为处理器不直接和磁盘等辅助存储器交互)

  • 控制总线:控制总线用于传递计算机发出的指令,它的位宽不固定,一般有处理器的架构和总线协议决定,同时控制总线中还包函时钟总线,用于同步整个总线上的各个设备。

  • 数据总线:数据总线用于处理器和总线上的设备间的数据传输,它的位宽为16,32,64,通常和处理器的字长相同。当一个设备接收到读的指令时,就会将地址总线上的地址处的数据写入数据总线,从而将数据传递给处理器。

计算机总线

IO设备的接口:

如上图,IO设备通过接口接入总线,接口一般由下面三个部分组成:

  1. 寄存器:IO设备的寄存器被映射到处理器寻址的空间当中,可以被处理器通过地址总线访问,通常由三种寄存器组成
    • 数据寄存器:用于存放接收到的或者要发送的数据
    • 状态寄存器:不同位的值代表着IO设备的各种状态
    • 控制寄存器:不同位的值控制着IO设备的工作模式
  2. 控制电路:读取控制总线上处理器发出的指令,执行相应的操作。
  3. 地址译码器:读取地址总线上的地址编号,判断处理器是否是在访问自己。

通过上述的接口结构,处理器只需要通过不断读取IO设备的状态寄存器,当状态寄存器里的值反映出数据寄存器存在数值,需要处理器进行读取时,处理器就会通过控制总线发送读取数据寄存器的命令,进而IO设备的控制电路会根据命令将数据寄存器中的数据放上数据总线将数据发送给处理器,这就是IO设备和处理器交互的过程。

IO设备的中断:

但是上面的这个过程存在过度占用处理器的问题——需要处理器不断地去读状态寄存器。如果IO设备上长时间上都没有数据输入,那么处理器一直在这里耗着这就是极大的浪费,因此这个时候就考虑使用中断来解决这个问题。

当使用中断时,处理器不必一直轮询IO设备的状态寄存器,而可以一直执行其它事情,当IO设备有数据输入的时候,发送一个称为中断请求的硬件信号给处理器(硬件信号的产生通常由特定事件造成的,比如当数据寄存器被填满这个事件发生时,IO设备就会发送数据读区中断的信号),让处理器停下手头的工作去执行中断处理函数(在这里就是读取IO设备的数据寄存器)。

一个很重要的地方就是中断请求信号必须向处理器表明是哪个设备申请的中断,这在有多个可以请求中断的设备是十分必要,否则处理器又要取挨个轮询每一个设备状态寄存器的IRQ位(这一位在设备发出中断请求后被置为1)来确定是谁发出的中断,如果同时有多个中断请求时,处理器也只会执行轮询到的第一个IRQ位为1的设备的中断服务函数。当中断请求信号可以表明自己是哪个设备发出的时,上述的问题都得到了解决,并且如果同时有两个中断请求,也可以根据事先对设备设置的优先级来决定哪个中断请求先被响应。

还有一个问题就是处理器如何根据不同设备的中断信号来判断该执行哪一个中断服务函数。这个过程通常是处理器通过查表来解决的,这个表就是中断向量表。中断向量表是由中断向量组成的,每一个中断向量都是一个指向对应中断服务函数的函数指针,当处理器接受到中断请求时它会进行查表,找到对应的中断向量,从而执行对应的中断服务函数。而这个表通常存放在存储器分配的一个永久的区域内。(在Stm32单片机中这个表写在启动文件中)

在IO设备的控制寄存器中应该有一位用来设置是否开启中断,处理器中也有专门的控制寄存器用来设置是否接收中断。值得注意的是中断实际上十分类似之前提到过的子程序的调用,它也是允许中断嵌套的。中断还有许多细节这里就不再过多阐述了。

总线仲裁:

当两个主控设备想要同时使用总线访问相同资源时就会有总线冲突,这时候就需要由总裁电路决定哪个设备先使用总线——这个过程就是总线仲裁。

仲裁链路的结构如下:

总线仲裁

每个设备与仲裁连接两根数据线——请求线和授权线,当两个设备同时向仲裁电路通过请求线发送总线的使用请求时,仲裁电路通过优先级判定哪个设备优先使用总线,并通过授权线允许设备使用总线,从而完成总线仲裁。

第四章:软件

1.汇编过程中符号的替换

将汇编文件转换成有机器指令组成的目标这个过程称为汇编。当负责汇编的程序扫描汇编文件的时候,会将变量的名字和它对应的值都记录在一个符号表中,然后每当它发现一个变量的时候就用表中的值将它替换掉。有一个需要注意的地方是,如果一个变量在它被定义前被当作操作数使用那么就无法查表替换它的值,负责汇编的程序一般通过二次扫描的方法解决这个问题——第一次扫描整个程序将所有变量的值记录在表中,第二次再将汇编文件中的变量名从头到尾替换成对应的值。

2.链接程序,装载程序

当一个程序需要多个汇编文件汇编组成时,汇编程序会将每一个汇编文件中引用的外部名(就是对其他文件中变量和函数的引用)和使用着写外部名的指令组成一个列表,并把这个列表包含在生成的目标文件当中。

链接程序在链接目标文件成为目标程序的过程中,需要根据每个目标文件中列表包含的信息,确定各个地址标签(也就是被其他文件引用的函数,变量)的相对位置(也就是是在各自文件中的位置),进而决定合成目标程序时每一个地址标签的绝对位置,进而生成目标程序。

生成的目标程序存储在存储器当中,当我们需要执行它的时候,需要将它从存储器当中装载到主存当中,然后将要执行的第一条指令的地址装载到程序计数器当中,这一过程由装载程序负责进行。装载程序通过来自用户的输入知道要执行哪条程序并在存储器中找到它,进而通过程序的头部信息知道这个程序的长度和要将它存放哪里(这些信息在汇编时由汇编程序放在目标文件的头部位置)

3.调试器和中断的使用

当程序没有按照我们预期的方式运行的时候,我们通常会使用调试器(一个程序)来找出问题所在,而调试器是通过中断实现调试工作的。当进行调试时处理器处于跟踪模式,每执行一条指令就产生一次中断(就是单步执行),并调用调试器程序中的中断服务函数,这时执行过程由调试器控制,通过服务函数用户就能查看寄存器和存储单元的值。当用户允许继续执行时,中断返回指令被执行,处理器接着执行被调试程序的下一条程序,接着再次进入中断。

断点使得调试更加灵活可以让程序能够在用户想要的地方“停下”,它通过一条称为陷阱或者软件中断的特殊指令来实现,当你在i指令前设置断点时,调试器会将i指令保存到一个临时的位置,并用一条中断指令替换它,当程序执行到断点的位置时就会触发中断,直到用户给出指令继续执行程序。当程序继续执行时,调试器还需要执行几个任务,首先它将i指令放回原位这时i指令将成为下一步第一条要执行的程序,然后调试器会在i+1指令处打上一个临时断点,这样程序在执行完毕i指令时就会再次进入中断当中,此时调试器会恢复i+1指令并恢复i指令处的断点,接着执行剩下的程序。

4.汇编语言与C语言交互

编译器不能根据高级语言的一个语句生成访问处理器控制寄存器的汇编指令,因此如果想用C语言程序实现访问控制寄存器的功能就需要将汇编语言嵌套在C语言中。我们可以通过一个特殊的指示符来告知编译器实现这个功能。

c
asm("MoveControl PS,R2");

还有一个问题就是编译器会将所有的C函数以RETURN_FROM_SUBROUTINE指令结尾(这是一个子程序调用的返回指令),但是由于中断服务函数的特殊性(它使用不同的堆栈,同时返回时需要恢复各个寄存器之前的状态),它需要以RETURN_FROM_INTERRUPT专门的指令结尾,因此用C语言编写中断服务函数的时候需要在函数的最后加上下面的代码:

c
asm("Return-from-interrupt");

这样一来中断服务函数在执行到这条指令时,处理器就会返回到之前的状态(通过这种方法修改的中断服务函数在编译后最后一条指令依旧是Return-from-subroutine但是当执行过到数第二条指令Return-from-interrupt后函数就不会继续执行了)

还有一种中方法是在终端服务函数前添加关键字interrupt来告诉编译器这是一个中断服务函数需要以Return-from-interrupt结尾,这样编译器就会自行替换,但是并非是所有编译器都支持这样的关键字。

5.引导程序

操作系统是一个庞大的程序组合,计算机的正常运行需要依靠操作系统帮助。在计算机正常运行的过程中操作系统存放在主存中,但是主存掉电是会丢失数据的,因此操作系统是需要存放在存储器当中并在开机时由引导程序逐步从存储器中加载到主存当中的。引导程序存储在一个固定位置,当处理器上电后会自动读取该位置的指令,在执行的过程中不仅会将操作系统加载到主存当中,同时还会对IO设备和主存进行必要的初始化。

第八章:存储器系统

存储器结构:

处理器执行程序的速度很大程度上依赖于数据在存储器和处理器之间的传输速度,理想的存储器应该满足读取存储速度快,容量大,价格便宜的特点,然而实际情况是这些理想的特点往往是矛盾的,而无法同时存在一种存储器身上,因此计算机使用了以下的存储器结构,充分利用各种存储材料的特点使处理器的性能最大化的发挥。

计算机存储器的结构

下面将依次介绍各个部分存储器的作用和所使用的存储材料。

存储器内部的组织结构:

存储器中一个存储单元存储一位数据,这些存储单元通常通过阵列的形式排列,每一行通过一根公共的线串联在一起,这根线称为字线,每一列通过两根位线连接在一起。下图是一个16x8结构(总共有16个字,每个字由8个位组成)的存储器电路。

16x8存储结构

  • 字线(word line):存储阵列每一行(也就是每一个字)和一根字线相连接,同时字线又和地址编译器相连接,我们在之前讲过地址译码器是和地址总线相连接的,每一根字线对应着一个地址。当地址译码器接收到地址总线上的地址时,相应的字线被激活也就是该行的存储单元被选中,这样位线才能读取到相应单元的数据。
  • 位线(bit line):存储阵列每一行和一根位线相连接,每一根位线包含负责读出和写入的两根线,当字线选中存储单元时位线就可以根据控制线的指令读出或写入该列上的存储单元,所有位线一起工作就可以同时读出选中行上的所有存储单元的内容。并且位线与数据总线相连接,可以将读取的数据放到数据总线上传回处理器,也可以将数据总线上的数据存储到单元中。

这样的存储结构需要14根与外部线连接的线就可以存储128位的数据(A0~A3这4根线负责选定16根字线,b0~b7这8位线和数据总线连接,还有2根控制线R/W负责确定是执行读还是写操作,CS用于芯片选择),使用这种方式组织1024位的存储阵列需要使用19根外部接线,但是如果换成1Kx1的组织形式就只需要15根外部接线。

1Kx1存储结构

这种排列方式下所有的存储单元被一根位线串联在一起,而字线被分成了两组,每一组都由32根字线组成,这两组字线由两个可以被同时访问的五位地址表示,分别代表行地址和列地址。当行地址确定时这一行上的所有存储单元都可以被访问,但是只有同时也被列地址选中的那个单元才能连接到数据线上。

RAM(随机存储器):

随机存储器(RAM,Random Access Memory)可以在任意时间随机访问存储单元,具有快速读写数据的能力。因此常用来作为计算机的主存。下面来介绍几种常见的随机存储器。

静态存储器(静态随机存储器)

静态存储器只要不断电就能一直保持它的存储状态。下图是静态随机存储器的结构图(SRAM)

静态随机存储器

当需要对该存储单元进行读写操作的时候,字线首先被激活来闭合T1和T2,如果进行读操作就只需检查此时位线上的电位,如果单元状态为1则读线(b)上为高电平,写线(b‘)上为低电平,如果状态为0则与之相反。如果进行写操作,就需要驱动读线和写线分别为相应的电位从而迫使单元改变状态为我们所希望的状态。

动态随机存储器(DRAM):

静态随机存储器的读写速度很快,但是由于每个单位由多个晶体管组成因此难以实现高密度的存储。而动态随机存储器才用更加简单的存储结构可以弥补SRAM的这些不足,但是简单的结构不能长时间存储,除非它经常被访问来进行读写操作这样在每次操作过程中它的状态就会被刷新。

DRAM结构

ADRAM和SDRAM:

动态随机存储器根据操作时序产生的方式不同又分为异步动态随机存储(ADRAM)和同步动态随机存储(SDRAM)。ADRAM通过芯片外部的一个存储控制电路产生的信号来控制读写操作的时序,而SDRAM通过时钟线来控制工作时序,并且内置了控制电路和刷新电路,可以通过内部的刷新计数器来指定要刷新的行地址来实现自动刷新。SDRAM在行被选定激活时每个时钟都将数据放到数据线上,能够做到很快的数据传输速度,并且为了追求更快的传输速率,人们设计了双倍速率动态随机存储器(DDR),它充利用了时钟速率,在时钟的上升沿和下降沿都传输数据。

ps.存储器一秒钟传输的字节数和位数被称为存储器带宽,这是一个衡量存储器性能的指标。存储器与处理器之间数据的传输速率是“存储器带宽”和“处理器到存储器连接的带宽”的函数。因此还有一个提升传输速率的方向是提高处理器到存储器连接的带宽(Rambus存储器的方案)。

ROM(只读存储器):

随机存储器都有在上电的时候才能保存数据,而某些程序需要一直存储在设备上(就像之前介绍的引导程序),这时就需要只读存储器(ROM)这种非易失性存储器来记录程序。ROM存储器中存储的信息仅在存储器生产的时候被被写入一次,之后便无法被更改。下图是ROM的结构。

image-20241122201255067

存储单元中存储的值由制造时通过一个值来决定。

PROM:

可编程只读存储器,它允许用户编程一次,在编程之前存储器P点插入了一根熔丝所有单元均为0,用户在编程时可以通过高电流来将特定单位的熔丝融化,从而使单元置1,但是这个过程是不可逆的因此只能编程一次。

EPROM:

可擦除可编程只读存储器,它的P点是通过一个特殊的晶体管接地的,正常状态下该晶体管是关闭状态,可以向其中注入电荷来使其开启,要驱散电荷需要将存储芯片从电路上物理移除并暴露在紫外线下,它通常用在项目开发阶段来方便程序迭代,但是它每一次擦除会将所有单元擦除掉。

EEPROM:

电可擦除可编程只读存储器,它可以有选择的擦除存储的信息,但是它在写入,读取,擦除时需要使用不同的电压增加了电路的复杂性。

Flash(闪存)

闪存也是一种非易失性存储器,它允许写入,读取和擦除,基本原理是利用浮栅晶体管(Floating Gate Transistor)来存储信息。以下是其主要工作原理:

  1. 存储单元:每个存储单元由一个浮栅晶体管组成,浮栅上储存的电荷表示存储的数据位(0或1)。
  2. 写入操作:在写入数据时,电流通过栅极使得电子进入浮栅,从而在该单元的电荷状态中存储数据。
  3. 擦除操作:擦除数据时,通过提供特定的电压,电子会从浮栅中被移除,改变数据位的状态。
  4. 读取操作:读取数据时,控制电路会检测存储单元的电压状态,以判断存储的数据是0还是1。

Flash存储器主要分为以下几种类型:

  1. NOR Flash
    • 结构:采用并行连接的存储单元,具有随机访问特性。
    • 应用:适用于需要快速读取和执行代码的场合,如BIOS存储和嵌入式系统。
  2. NAND Flash
    • 结构:采用串联连接的存储单元,效率高,存储密度大。
    • 应用:广泛用于USB闪存驱动器、固态硬盘(SSD)和移动设备中。

NOR Flash由于具有随机访问的特性,可以作为嵌入式芯片的主存用于存放需要执行的指令,而NAND Flash由于它的存储密度大,代替磁盘成为了主流的辅助存储器

磁盘:

也叫机械硬盘,通过磁针和磁盘存储数据,特点是存储空间大,成本低,但是由于读写速度较慢,体积较大正逐渐被固态硬盘取代。

再谈存储结构:

回到最开始所说的存储结构,不同的位置存储器要根据它需要实现的功能来选择不同特点的存储材料。

计算机存储器的结构

从上向下来看除了处理器的寄存器在处理器内部以外,主(一级)高速缓存也在芯片内部,还有二级高速缓存和三级高速缓存它们的存储空间比一级高速高速要大一些但是读写速度要慢一些,它们通常是由SRAM技术实现的。

再往下来就到了主存储器部分。这个部分的存储空间相当大,但仍处于处理器地址映射的部分,可以由处理器直接访问,用于存放正在运行的程序以及堆栈,通常使用DDR来制作而成,它的读写速度也相当快(与高速缓存相去甚远)但是掉电会丢失数据,因此还需要辅助存储器来在掉电的时候来保存数据。

最后一级就是辅助存储器,它需要满足掉电不丢失,存储空间大的需求。因此一般使用磁盘,或者固态硬盘作为辅助存储器,由于处理器并不直接和辅助存储器进行交互,所以它对速度的要求并没有前面的存储设备高。

存储结构的工作机制:

我们之前讲到过处理器组织内存单元的方式是按字组织,并为每一个字节的内存空间(包括IO设备的寄存器空间)分配一个唯一的地址,这些唯一的地址组合在一起就是处理器的寻址空间。处理器的寻址方式是通过在地址总线上放上寻址空间内的地址来与目标存储单元进行数据的交互。

处理器的逻辑寻址空间的大小由地址总线的位数来决定,例如32位的总线最多访问4GB的空间(更大的地址总线就无法表示)。而实际的寻址空间是要小于等于逻辑寻址空间的,它的大小由组成它的主存和IO设备寄存器的内存空间来决定,不包括辅助存储器(虽然上辅助存储器也可以看作IO设备但是它的访问需要通过操作系统),寻址空间内的存储空间可以由处理器通过总线直接访问。

程序是在主存中执行的,执行的指令和产生的中间数据都存放在主存当中。数据在主存和处理器之间的传递速率会对程序执行的速率产生巨大的影响,需要尽可能的减少数据传递耗费的时间来提高执行效率,而高速缓存就是为此而生的。它是一个很小但是传输速率很快的存储器,在存储结构中放在主存和处理器之间,它依据程序的引用局部性(就是正在执行的指令的附近的指令和刚执行过的指令很快被再次执行到的概率较大)将可能会被执行到的指令提前存放在高速缓存当中,如果缓存中的指令真的被执行到了就直接从高速缓存中加载指令,这样一来就大大节省了时间(高速缓存传递数据能比主存快100倍)。

我们希望能得到更大的主存空间来运行更多的程序,通常会使用虚拟主存(实际上就是把辅助存储器当作主存使用)来扩大主存空间。举个例子,操作系统会把程序执行到的部分加载到主存当中,而未执行到的部分存放在辅助存储器上,由于实际寻址空间往往要比逻辑寻址空间小,因此操作系统使用这些未分配给任何存储空间的逻辑地址来“欺骗”处理器,让它认为这些地址是剩下的程序的地址,也就是让处理器“误以为”剩下的程序也在主存上(实际上这些程序还在辅助存储器当中,只是在处理器的角度看来这些数据都在它的寻址范围内它可以通过地址来直接访问)。当处理器使用虚拟地址(在实际寻址地址范围外却在逻辑寻址范围里)去访问未执行到的程序时,虚拟地址指向的内存空间并不存在,但是在这条指令传递的过程中内存管理单元(MMU)会将虚拟地址转换成一个实际存在的地址,并通知操作系统使用DMA将剩下的程序从辅助存储器中传送到转换的实际地址,使处理器完成程序的访问,这个过程如下图所示:

虚拟地址转换