存储器的分类
1、易失性半导体称为RAM
2、非易失性半导体称为ROM
可以粗略的这样理解,RAM一般用来做内存,ROM一般用来做外存。ROM由于其非易失性,一般拿来写BIOS这种写死的硬件程序。(不绝对)
存储器的性能指标
1、存储容量:一般按照存储字数×字长的格式表示
2、单位成本:位价 = 总成本/总容量
3、存储速度:数据宽度/存取周期
存取周期指进行一次完整的读写操作所需的全部时间
存储器的多层级系统
为了解决存储系统大容量、高速度和低成本的三个相互制约的矛盾,在计算机系统中,常常采取多级存储器的结构,如下所示
存储器多层级系统的核心思想是上一层的存储器作为低一层存储器的高速缓存
其中,主存和cache之间的调度是由硬件自动完成,对所有程序员透明
主存和磁盘之间的调度由操作系统和硬件共同完成,对应用程序员透明
同时,我们还可以使用并行计算的技术,每次存取多个字,同时计算/处理多个字来加速运算。
CPU和存储器的链接模型
总体而言,可以概括为下图
CPU通过地址总线和读写信号控制主存,主存通过数据总线和准备信号返回给CPU
SRAM存储器
以上为SRAM的存储结构。对于地址,这里的X译码器和Y译码器分别对行和列进行译码,额外还有一个译码器进行片选,这里没有画出来。
这里看到的存储矩阵一共有16片,这16片分别代表数据的16位。也就是说,这是一个16位的机器。
存储器的容量扩充(重点)
一片存储器的容量是有限的,使用多片存储器芯片才能构造一个具有一定容量和字长的存储器。个人理解,存储器这部分没啥好考的,就这里最重要,所以一定要注意。
位拓展法
是更充分的利用数据总线的一种方式。在不增加任何地址线的基础上,多增加的存储器并联到数据总线上,实现位的增加。简单来说,本来有8K×8位的一个存储器,经过位拓展后,变为了8K×16位的存储器。
不过,需要注意这种拓展方法的上限和数据线的宽度有关。
字拓展法
是纵向的对于存储器的一种堆叠。但是需要利用更多的地址线(可以理解为是更充分的利用地址线的一种办法)。我们利用更多的地址线进行片选存储,是单纯的存储数据的扩展。
字位同时拓展法
字位同时拓展可以理解为,先对存储器进行位拓展,然后把拓展后的存储器作为一个单元,对这个单元进行字拓展。
SRAM时序
SRAM读周期与写周期综合说明
1. 读周期(Read Cycle)
功能:从指定地址读取数据并输出到I/O端口。
关键参数
- 读出时间
从地址有效到数据稳定输出在I/O端口所需的时间。
- 读周期时间
两次连续读操作之间必须满足的最小间隔时间,确保存储器完成内部操作(如预充电)。
时序过程
- 地址输入
提供有效地址信号,指定要读取的存储单元。
- 片选与输出使能
- 片选信号
为低电平,选中SRAM芯片。
- 输出使能信号
为低电平,允许数据输出。
- 片选信号
- 数据输出
- 地址译码后,选中存储单元,数据通过位线传输。
- 经过
时间后,数据稳定输出到I/O端口。
- 地址译码后,选中存储单元,数据通过位线传输。
- 读周期结束
在 时间后,可开始下一次读操作。
控制信号
(片选信号):选中SRAM芯片。
(输出使能信号):使能数据输出路径。
2. 写周期(Write Cycle)
功能:将数据写入指定地址的存储单元。
关键参数
- 写周期时间
完成一次写操作所需的总时间。
- 地址建立时间
在 有效前,地址信号必须保持稳定的时间。
- 写数据有效时间
在 有效后,输入数据必须保持有效的时间。
- 数据保持时间
在 恢复高电平后,数据仍需保持不变的时间。
时序过程
- 地址输入
提供有效地址信号,指定要写入的存储单元。
- 片选与写使能
- 片选信号
为低电平,选中SRAM芯片。
- 写使能信号
为低电平,允许数据写入。
- 片选信号
- 数据输入与写入
- 输入数据必须在
有效后 时间内保持稳定。
- 数据通过I/O端口写入选中的存储单元。
- 输入数据必须在
- 写周期结束
在 时间后,写操作完成,可进行下一次操作。
控制信号
(片选信号):选中SRAM芯片。
(写使能信号):使能数据写入路径。
3. 对比与总结
参数/过程 | 读周期(Read) | 写周期(Write) |
---|---|---|
核心功能 | 从存储单元读取数据 | 将数据写入存储单元 |
关键信号 | ||
关键时间参数 | ||
数据流向 | 存储单元 → I/O端口 | I/O端口 → 存储单元 |
时序约束 | 确保数据稳定输出( |
确保地址和数据有效时间( |
4. 注意事项
- 时序匹配
- 系统设计需满足
和 的约束,避免操作冲突。
- 写操作需确保地址和数据在
和 内稳定。
- 系统设计需满足
- 信号时序
和 的时序需与地址、数据信号严格配合。
- 数据稳定性
- 读周期中,数据在
时间前可能处于高阻态(High-Z) 或 过渡状态(时序图中显示为黑色区域)。
- 写周期中,数据需在
有效期间保持稳定,否则可能导致写入错误。
- 读周期中,数据在
DRAM存储器
特点
DRAM相比于SRAM来说,具有密度高,位价低,和功耗小的优点,但是DRAM的读取速度相对较慢,并且是破坏性读出,因此必须定时刷新和读后再生。
刷新方式
首先,刷新基本上只能以存储器的行为单位,并且,我们将对同一行进行相邻两次刷新的时间间隔称为
1) 集中刷新
在一个刷新周期内,利用一段固定的时间,集中对所有存储器的所有行进行逐一再生,在此期间暂停读/写操作,称为死时间。
2) 分散刷新
把读写操作和刷新周期合成一个工作周期,前半部分分配给读写操作,后半部分分配给刷新操作。这样就没有死区了。
3) 异步刷新
使得一个刷新周期内,==每一行只刷新一次==。具体来说,就是把每一行的刷新的时间分开,将刷新周期除以行数,得到相邻两行之间刷新的时间间隔t,每隔t时间,产生一次刷新请求,这就使死时间更为分散。
读取时序
其中,RAS指的是行选通信号,CAS是列选通信号
引脚复用技术
由于DRAM芯片容量很大,地址位数较多,为了减少芯片的地址引脚数,我们复用行地址和列地址的输入引脚,分两次不同按照不同的时序输入。
ROM存储器
- PROM:一次编程只读存储器。
出厂时全为0,用户可选择把一些内容改为1。
- 多次编程只读存储器:主要讲了UV-EPROM和EEPROM,比起PROM增加了擦除能力
- 闪存存储器:相比EPROM,Flash
Memory在擦除过程更加的迅速和便捷,最早的东芝的NAND闪存只支持按页存取数据,故无法直接替代传统ROM芯片。但是后来,Intel推出了NOR闪存,读取很快,但是写入时间较长。可以直接替换EPROM。
并行存储器
双端口存储器DPRAM
结构如下所示
但是这种并行的存储会造成冲突。显然,当两边同时对同一存储单元进行写入的时候,会造成读写冲突。为了解决这个冲突,我们设计了
1、判断逻辑部件决定哪个端口优先读写
2、将另一侧的
所以重点在判断逻辑部件上
判断逻辑分为两种
当地址匹配先于CE#冲突时,采用CE仲裁
当CE#先为有效位的时候,采用地址仲裁
多模块存储器
核心技术原理
多模块存储器本质上采取的是一种空间并行技术,利用多个结构完全相同的存储模块的并行工作来提高存储器的吞吐率。
(对于这部分,我询问了Gemini,我觉得Gemini给出了我很好理解的一种方式,在这里分享给大家)
好的,完全没问题。我们一步一步来,从最基础的概念开始,把整个逻辑链条串起来。
第一站:问题的根源——CPU 和内存的速度差异
想象一下,你的 CPU 是一个速度极快的顶级大厨,每秒钟能处理 100 道菜。而你的内存(主存)是一个普通的仓库管理员,从货架上取一份食材需要 10 秒钟。
矛盾:大厨(CPU)处理完一道菜,想要下一份食材时,必须干等 10 秒,等仓库管理员(内存)慢悠悠地取来。CPU 的大部分时间都浪费在等待上。
怎么解决这个“等待”问题?工程师们想出了两种主要的办法。
第二站:方法一:单体多字存储器 (The “一次多拿” Strategy)
想法:既然去一趟仓库这么慢,那不如让管理员一次性多拿几份食材回来,万一待会儿用得上呢?
结构:
- 单体:只有一个仓库(一个存储模块)。
- 多字:这个仓库的货架设计得很宽,管理员伸一次手,就能同时拿到 4 份连在一起的食材。连接仓库和厨房的路(总线)也修得特别宽,能让一辆大卡车(而不是手推车)一次运回这 4 份食材。
工作流程:
- 大厨(CPU)说:“我需要第 0 号食材(比如:指令 0)”。
- 仓库管理员(内存)去第 0 号位置,不仅拿了第 0 号,还顺手把旁边的
1、2、3
号食材也一起拿了回来,装上大卡车,一次性运到厨房门口的临时存放区(高速缓存或指令缓冲器)。这整个过程花了
10 秒。
- 大厨用完 0 号食材,喊:“要 1 号!”。他一转身,发现 1 号食材已经在临时存放区了,立刻就能拿到。然后 2 号、3 号也一样,都不用等。
优点:
如果大厨严格按照 0, 1, 2, 3… 的顺序使用食材,这个方法效率极高。一次等待,多次享受。
致命缺点 (你提到的那个):
如果大厨用完 0 号食材,突然说:“菜谱变了,我需要第 100 号食材!”(这在程序里就是 JUMP 或 GOTO 跳转指令)。
那么,刚刚辛辛苦苦运回来的 1、2、3 号食材就完全作废了,白白浪费了存储和传输带宽。现在,系统必须重新启动一次完整的、缓慢的“大卡车运输”流程,去仓库取 100、101、102、103 号食材。
小结:“一次多拿”策略在处理连续数据流时很棒,但在跳转时非常笨拙和浪费。
第三站:方法二:多体并行存储器 (The “团队协作” Strategy)
想法:一个仓库管理员太慢,那就雇佣多个管理员,让他们在多个独立的小仓库里同时工作!
结构:
我们不再建一个巨大的仓库,而是建 4 个独立的小仓库,分别叫模块0 (M0)、模块1 (M1)、模块2 (M2)、模块3 (M3)。每个仓库都有自己的管理员,可以独立、并行地工作。
核心问题:现在食材(数据和指令)应该如何在这 4 个仓库里摆放呢?这就引出了两种不同的“编址”方案。
方案A:高位交叉编址 (顺序存放)
- 存放方式:把 0~99 号食材全部放 M0,100~199 号全部放
M1,200~299 号全部放 M2,以此类推。
- 地址解读:一个地址(比如
150)的高位(1)决定了去哪个仓库(M1),低位(50)决定了在仓库里的哪个位置。
地址 0
: M0, 位置 0
地址 1
: M0, 位置 1
地址 99
: M0, 位置 99
地址 100
: M1, 位置 0
- 效果:当大厨需要 0, 1, 2… 号连续食材时,请求总是发往同一个模块 M0。M0 的管理员忙得不可开交,而 M1, M2, M3 的管理员都在闲着。这又回到了最初的“等待”问题,没有发挥出团队协作的优势。
方案B:低位交叉编址 (交错存放) —— 这就是重点!
存放方式:我们把食材轮流、交错地摆放。
- 0 号食材放 M0
- 1 号食材放 M1
- 2 号食材放 M2
- 3 号食材放 M3
- 4 号食材再放回 M0
- 5 号食材再放回 M1
- … 以此类推,像发扑克牌一样。
- 0 号食材放 M0
地址解读:一个地址的最低位决定了去哪个仓库,高位决定了在仓库里的哪个位置。
地址 0 (二进制 ...00)
: 低两位是 00 -> 去 M0
地址 1 (二进制 ...01)
: 低两位是 01 -> 去 M1
地址 2 (二进制 ...10)
: 低两位是 10 -> 去 M2
地址 3 (二进制 ...11)
: 低两位是 11 -> 去 M3
地址 4 (二进制 ...100)
: 低两位是 00 -> 回到 M0
效果(见证奇迹的时刻):
- 时刻 t0: 大厨要 0
号食材。总指挥(内存控制器)把任务派给 M0。M0
开始工作(需要 10 秒)。
- 时刻 t1: 大厨要 1 号食材。总指挥把任务派给
M1。由于 M1
是独立的,它可以立即开始工作,完全不用管 M0
在干嘛。
- 时刻 t2: 大厨要 2 号食材。任务派给
M2,M2 立即工作。
- 时刻 t3: 大厨要 3 号食材。任务派给
M3,M3 立即工作。
- 时刻 t4: 大厨要 4 号食材。任务派给 M0。此时,距离 t0 已经过去了足够长的时间,M0 的第一个任务(取 0 号食材)很可能已经完成了!它可以无缝衔接开始取 4 号食材。
- 时刻 t0: 大厨要 0
号食材。总指挥(内存控制器)把任务派给 M0。M0
开始工作(需要 10 秒)。
最终结果:通过这种巧妙的安排,四个模块就像工厂里的流水线一样重叠工作。虽然每个模块单独完成任务需要 10 秒,但由于它们是并行启动的,整个系统在“预热”之后,几乎每个时钟周期都能向 CPU 交付一个数据。
终极对比与理解
- 单体多字:像一个大力士。一次搬运很多东西,但如果搬错了(发生跳转),前面的力气就白费了。
- 高位交叉:像一个分门别类的大图书馆。找历史区的书(连续地址)还是得在历史区里一本一本地等。适合多个
CPU 同时访问不同区域(一个查历史,一个查科学)。
- 低位交叉:像一个高效的流水线团队。把一个连续的任务(取连续地址的数据)分解给团队成员(不同模块)并行处理,极大地提高了处理连续请求的整体速度。这才是真正解决了 CPU 和内存速度差距问题的关键技术。
希望这个从零开始的、带比喻的讲解能帮助你彻底理解它们之间的区别和演进关系。
单体多字存储器
在单体多字系统中,主要拓宽的就是存储器的存储字长,每个存储单元存储m个字,总线宽度也为m个字,一次并行读出m个字,在一个存取周期内,从同一地址取出m条指令,然后将指令逐条送到CPU执行。
缺点:只有连续存放时,一次并行读出的数据才会是有效的指令,否则后面读出的都无效了
多体并行存储器
由多个模块组成,每个模块有相同的容量和存取速度,方便并行工作。
高位交叉编址
跟直接拓展存储器差不多,就是对高位地址改为片选,低位地址为存储器内部选数据使用
低位交叉编址
跟高位相反,此时低位地址为模块的片选。
计算存取时间和带宽
连续取m个字的时间:
当然,这里隐含了一个前提,就是模块数m,必须满足
Cache存储器
motivation
由于DRAM,硬盘和CPU的速率之间差异过大。为了提高总体的使用速率,让系统的响应时间更快,更平滑,我们就引入了高速缓存Cache
程序访问的局部性原理
程序访问的局部性原理包括时间局部性和空间局部性。
- 时间局部性:最近的未来使用的信息,是现在正在使用的信息。
- 空间局部性:未来要用到的信息,和现在使用的信息在空间上是临近的。
高速缓冲技术就是利用同部性原理,把程序中正在使用的部分数据存放在一个高速的、容量较小的Cache中,使CPU的访存操作大多数针对Cache进行,从而提高程序的执行速度。
Cache的基本工作原理
为便于Cache与主存交换信息,Cache和主存都被划分为大小相等的块(数据交换的最小单位)。
由于Cache的容量较低,因此Cache的块的数量也少很多。Cache中仅保存主在中最活跃的若干块的副本,因此,可按照某种微略预测CPU在未来一段时间内做访存的数据,将其装入Cache。
注意,Cache和CPU之间的数据交换以字为单位,而Cache与主存之间的数据交换则以Cache块为单位。
当CPU发出读请求时,若访存的地址就在Cache中(称为Cache命中),就将此地址转化为Cache地址,直接对Cache进行读操作,不对主存进行操作。
若Cache不命中,则仍需访问主存,并把此字所在的块一次性地从主存调入Cache中。
若此时Cache已满,则需根据某种替换算法,用这个块替换Cache中原来的某块信息,整个过程全部由硬件实现
当CPU发出写请求时,若Cache命中,则需要按照一定的写策略处理(详见后续Cache的一致性问题),常见的处理方法有全写法和回写法;如果未命中,那么直接写入主存。
对于cache未命中的情况,我们明明为Cache miss,即Cache缺失
Cache缺失的类型
- 冷启动缺失:首次访问某个数据块时,缓存中必然不存在该数据,必须从主存加载
- 冲突缺失:缓存采用组相联或直接映射策略时,多个数据块映射到同一缓存位置(组),导致频繁替换。即使缓存未满,也会因位置冲突引发缺失。
- 容量缺失:缓存容量不足,无法容纳程序频繁访问的所有数据块,导致部分数据被替换出去,后续再次访问时发生缺失。
Cache和主存的映射方式
Cache行数比主存块数少得多,因此主存中只有一部分块的信息可放在Cache中,因此在Cache中要为每块加一个标记位,指明它是主存中哪一块的副本。该标记的内容相当于主存中块的编号。为了说明Cache行中的信息是否有效,每个Cache行需要一个有效位
Cache行中的信息是主存中某个块的副本,地址映射是指把主存地址空间映射到Cache地址空间,即把存放在主存中的信息按照某种规则装入Cache。
直接映射
主存中的每一块只能装入Cache中的唯一位置。若这个位置已有内容,则产生块冲突,原来的块将无条件地被替换出去。
这种方法很笨,很容易导致块冲突,并且空间利用率低。
在直接映射的过程中,CPU首先根据行号,查询到对应的cache行,然后再将该块中的标记和主存中的地址进行比较(由于标记不一定和主存地址一样长,因此将主存地址阶段到和标记一样长进行比较),如果比较成功,那么说明命中。根据块内地址,将主存写入对应的块中。
如果未命中,则将标记修改为对应的主存地址,然后将有效位置1,同时将该地址中的内容送入Cache行。
全相联映射
主存中的每一块可以装入Cache中的任意位置。由标记为指出该行的信息来自主存的哪一块。
这种方法冲突概率低,空间利用率高。但是标记的比较速度慢,并且成本高,需要CAM(按内容寻址存储器)
全相联映射的地址结构为:
访存过程如下:
1)
将主存地址的高位和Cache各行的标记进行比较,如果有一个块结果为1,则命中
2) 根据块内地址取出信息。
3) 若无相等,则不命中
4)
从主存读出该地址所在的信息送到Cache的任意一个空闲行中,将有效位置1,并设置标记。
通带为每个Cache行都设置一个比较器,比较器位数等于标记字段的位数。访存时根据标记字段的内容来访问Cache行中的主存块。
组相联映射
将Cache分成Q个大小相等的组,每个主存块可以装入固定组中的任意一行,即组间采用直接映射、而组内采用全相联映射的方式。它是对直接映射和全相联映射的一种折中。
当Q=1时,退化为全相联映射,当Q=Cache行数的时候,退化为直接映射。
调整Q的数量,可以使组相联映射的成本接近直接映射,而性能上仍接近全相联映射。
假设每组有r个Cache行,则称为r路组相联映射
访存过程如下:
1) 根据组号找到对应的Cache组
2)
将Cache组中的每个行标记和主存地址的高位标记进行比较,若有一个相等且有效位为1,那么命中
3) 此时根据块内地址,在对应的Cache行中存取信息
4)
若不命中,则将对应的Cache组内的任一空闲行标记修改为对应的主存地址,然后将有效位置1,同时将该地址中的内容送入。
由于r路相联映射一共需要和r个Cache行进行比较,因此就需要r个比较器。
这里有个很神奇的地方:
经过我的计算,只要确定了组号的编码位数和块内地址的位数,只需要用物理地址的空间的编码位数减去前两个位数,就可以得到标记的位数。其原因在于,低位位数相同的部分,一定在同一个组的内,因此,只需要标记部分就可以确定是否命中。
也可以这么理解,组号和块内地址的编码“屏蔽”了低位部分的判断,因为对于一个组内的块来说,他们的组号一定是一致的,而块内地址由和原本的地址的低位部分重合。因此就不需要
总结
对比项 | 直接映射 | 全相联映射 | 组相联映射 |
---|---|---|---|
映射规则 | 主存块固定映射到唯一 Cache 行 | 主存块可映射到任意 Cache 行 | 主存块映射到固定组内的任意行(组间直接映射,组内全相联) |
地址结构 | IMG/direct_mapping_addr.png | IMG/associative_addr.png | IMG/set_associative_addr.png |
比较器数量 | 1 个(每行固定位置) | n 个(n = Cache 总行数) | r 个(r = 每组行数) |
冲突概率 | 最高(固定位置易冲突) | 最低(任意位置避免冲突) | 中等(组内灵活映射) |
空间利用率 | 最低(易产生空闲行) | 最高(完全灵活使用空间) | 较高(组内灵活) |
查找速度 | 最快(直接定位+1次比较) | 最慢(全表遍历比较) | 较快(组定位+r次比较) |
硬件成本 | 最低(无需复杂电路) | 最高(需大量比较器/CAM) | 中等(需r个比较器) |
控制复杂度 | 最简单 | 最复杂 | 中等 |
典型应用场景 | 低成本嵌入式系统 | 小容量高速缓存(如 TLB) | 通用处理器 Cache(折中方案) |
Cache替换算法
- RAND(随机选择)法:由硬件随机选择一个Cache数据块进行替换
- FIFO(先进先出)法:把最先调入Cache数据块作为被替换对象
- LFU(最少使用)法:为每个Cache行设置访问计数器,将最近一段时间内被访问次数最少的Cache块换出
- LRU(最久未使用)法:为每个Cache行设置访问计时器,将近期最长未被访问过的Cache块换出
Cache的一致性问题
全写法
当CPU对Cache写命中时,必须把数据同时写入Cache和主存。如果未命中,且Cache已满,则需要替换一个块,替换的过程中,被替换掉的内容无需再写回主存。只需要将新写入的数据也写入主存即可。
同时,为了减少全写法写入主存之间的时间损耗,在Cache和主存之间增加一个写缓冲。写缓冲是一个FIFO队列,可以解决速度不匹配的问题。
回写法
CPU对Cache写命中时,只把数据写入Cache,而不立即写入主存,只有当此块被替换出时才写回主存。
这种方法减少了访存次数,但是增加了数据不一致的风险。为了减少写回主存的次数,给每个Cache行设置一个修改位(脏位)。若修改位为1,则说明对应Cache行中的块被修改过,替换时须写回主存;若修改位为0,则说明对应Cache行中的块未被修改过,替换时无须写回主存。
Cache写操作不命中
- 写分配法。更新主存单元,然后把主存块调入Cache。
- 非写分配法,只更新主存单元,而不把主存块调入Cache。
Cache的性能指标
- 命中时间:从Cache传送一个字到CPU所需的时间
- 命中率:
其中, 代表在Cache中完成存取的总次数, 代表在主存中完成存取的总次数。命中率说明了程序的局部性原理的利用情况。
- 缺失率:
由于一般来说,Cache的命中时间要远远小于从主存到Cache的时间,因此,缺失率可以更好的体现性能。
- 平均访问时间(有效的访问时间):
其中, 表示命中时Cache访问时间, 表示未命中时的主存访问时间。显然, 越接近 越好
- 访问效率:
其中,r代表主存慢于Cache的倍率,公式为
- 缺失损失:加载低一层中的数据到当前层的时间+将数据交给CPU的时间
虚拟存储器
核心目的:虚拟存储器将主存或辅存的地址空间统一编址。形成一个庞大的地址空间,在这个空间内,用户可以自由编程,而不必在乎实际的主存容量和程序在主存中实际的存放位置。
概念
- 虚地址:用户编程允许设计的虚地址
- 虚拟空间:虚地址对应的存储空间
- 物理地址:实际的主存单元的实际地址
- 页:之前在Cache中,交换信息的最小单位是块。在这里,最小单位则是页。
使用过程
CPU使用虚地址时,先判断这个虚地址对应的内容是否己装入主存。若己在主存中,则通过地址变换,CPU可直接访问主存指示的实际单元若不在主存中,则把包含这个字的一页或一段调入主存后再由CPU访问。若主存已满。则采用替换算法置换主存中的交换块
细节
- 由于需要把辅存中常用的数据页装入主存中:
- 处理一致性问题,由于写入磁盘的开销过大,因此采用回写法。
- 主存和辅存之间采用全相连映射
- 处理一致性问题,由于写入磁盘的开销过大,因此采用回写法。
页式虚拟存储器
基本概念:页式虚拟存储器以页为基本单位。主存空间和虚拟地址空间都被划分成相同大小的页,主存空间中的页称为物理页、实页等,虚拟地址空间中的页称为虚拟页、虚页。页表记录了程序的虚页调入主存时被安排在主存中的位置。页表一般长久地保存在内存中。
页表
- 有效位:用来表示对应页面是否在主存
- 脏位:用于表示页面是否被修改过
- 引用位:用于配合替换算法表示最近是否使用过等
缺页处理
若有效位为0,则发生缺页异常,调用缺页异常处理程序。根据表中的存放位置字段,从主存调入一个空闲的物理页框。若主存没有空闲的页框,则还需要选择一个页面进行替换。然后根据脏位确定是否写回磁盘。
优缺点
- 优点:页面的长度固定,页表简单,调入方便
- 缺点:最后一页浪费空间。页不是逻辑上独立的实体,所以处理、保护和共享都不及段式虚拟存储器方便。
地址转换
概念:
- 虚拟地址:两个字段,高位为虚页号,低位为页内偏移地址。
(可以看作是两级索引)
- 物理地址:两个字段:高位为物理页号,低位为页内偏移地址。
- 页表基址寄存器:存放进程的页表首地址
由于页面大小一致,因此页内偏移地址也是一致的。
转换过程:
1)
在页表基址寄存器中找到页表首地址,然后根据虚页号找到对应的页表项。
2)
查看页表项的装入位,若为1,那么直接取出其物理页号,和页内偏移地址拼装
3) 若为0,那么执行缺页处理程序
TLB快表
目的:由地址转换过程可知,访存时先访间一次主存去查页表,再访问主存才能取得数据。若缺页,
则还要进行页面替换、页面修改等,因此采用虚拟存储机制后,访问主存的次数更多了。
措施:创建一个由高速缓冲器组成的快表,本质是一个特殊的cache。在一段时间内总是访问某些页的时候,若把这些页的页表放在高速缓冲器组成的快表中,则可以提高效率。在地址转换的时候,总是先查找快表,若命中,则无需访问主存中的页表。
在查找的过程中,若采用全相连映射,那么标记直接采用虚拟页号
若采用组相连映射,TLB标记则是对应虚拟页号的高位部分。低位部分可以确定组号。(因为模运算可以屏蔽掉低位的不同,确保在对应的组内的高位部分可以唯一确定对应的存储位置)
段式虚拟存储器
段的概念
- 逻辑划分:
段是程序的逻辑单元,通常对应程序的自然模块(如代码段、数据段、堆栈段等)。每个段的大小可以动态调整,而非固定大小(与分页不同)。- 代码段:存放程序的可执行指令。
- 数据段:存放全局变量、常量等静态数据。
- 堆栈段:用于动态分配内存(堆)和函数调用(栈)。
- 代码段:存放程序的可执行指令。
- 动态长度:
段的长度不固定,可以根据程序运行时的需求动态增长或收缩。例如,堆段可能随着内存分配而扩展。
- 独立性:
每个段是独立的逻辑实体,具有自己的访问权限(如只读、可写、可执行),便于共享和保护。
- 段表:程序逻辑段和存放地址的对照表,每行记录与某个段对应的段号、装入位、段起点和段长等信息
地址转换过程
- 根据段表基地址和段号拼接成对应的段表项,根据装入位判断该段是否已调入主存。
- 若装入位为1,那么从段表直接读出对应在主存中的起始地址,和段内地址相加得到对应的主存实地址。
优缺点
因为段本身是程序的逻辑结构所决定的一些独立部分,因此分段对程序员来说是不透明的。而分页对程序员来说是透明的,程序员编写程序时不需知道程序将如何分页。
-
优点:段的分界与程序的自然分界相对应,因此其有逻辑独立性,使得它易于编译、管理、修改和保护,也便于多道程序的共享
段页式虚拟存储器
其实可以看作是,一个三级页表,但是其中的二级页表是按照逻辑结构来的!
多级页表
本质就是,一个多级索引的图书馆。
每一级的索引都指向一个页表项(可以看作是一个小的索引),地址中的对应长度为其虚地址(该索引中的位置)
直到最后一个,才能得到其物理地址。
如果您喜欢我的文章,可以考虑打赏以支持我继续创作.