操作系统(11)----内存管理
内存管理分类如下:
目录
一.地址转换
1.绝对装入
2.可重定位装入(静态重定位)
3.动态运行时装入(动态重定位)
二.内存保护
1.设置上、下限寄存器
2.重定位寄存器和界地址寄存器
三.内存空间的扩充
1.覆盖技术
2.交换技术
3.虚拟存储技术
•虚拟内存的定义
•虚拟内存的特征
•实现虚拟内存技术的方法
(1)请求分页管理技术
(2)页面置换算法
四.内存空间的分配与回收
1.连续分配管理方式
(1)单一连续分配
(2)固定分区分配
(3)动态分区分配
2.非连续分配管理方式
(1)基本分页存储管理
•页框和页
•页表
•基本地址变换机构
•具有快表的地址变换机构
•两级页表
(2)基本分段存储管理
•分段
•段表
•地址变换
•分段和分页管理的对比
(3)段页式存储管理
•分页和分段的优缺点
•段页式管理的优点
•段表、页表
•逻辑地址转为物理地址
一.地址转换
在上一小节中,我们讲到的模块装入方式,就是从逻辑地址转换为物理地址的方式,我们来回顾一下:
1.绝对装入
绝对装入:在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。(也就是将编译程序中各变量的地址修改为正确的绝对地址)
例如:如果直到装入模块要从地址为100的地方开始存放
装入内存,从100开始装入:
缺点:灵活性很差,只适用于单道程序环境。
2.可重定位装入(静态重定位)
静态重定位:又称可重定位装入。编译、链接后的装入模块的地址都是从0开始的,指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,将装入模块装入到内存的适当位置。装入时对地址进行“重定位”,将逻辑地址变换为物理地址(地址变换是在装入时一次完成的)。
与绝对装入的区别在于,静态重定位是在程序装入内存时,再进行地址转换,将逻辑地址转为物理地址。
静态重定位的特点:
•在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。
•作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。
•一般用于早期的多道批处理操作系统。
3.动态运行时装入(动态重定位)
动态重定位:又称动态运行时装入。编译、链接后的装入模块的地址都是从0开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行。因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持。
重定位寄存器:
存放了装入模块存放的起始位置,例如下图,起始位置为100。
当CPU访问指令0时,会将其中的逻辑地址79,与重定位寄存器的起始位置相加,得到其访问的地址。
与静态重定位的区别在于,动态重定位装入时依旧保持使用逻辑地址。
动态重定位的特点:
•采用动态重定位时允许程序在内存中发生移动。
•可将程序分配到不连续的存储区中;
•在程序运行前只需装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存;
•便于程序段的共享,可以向用户提供一个比存储空间大得多的地址空间。
二.内存保护
内存保护即操作系统需要保证各进程在各自存储空间运行互不干扰。
这里假设进程1的逻辑地址空间0~179;实际物理地址空间为100~279;
1.设置上、下限寄存器
在CPU中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,CPU检查是否越界。
CPU会根据上下限寄存器中存储的数据,来判断进程访问某个地址时是否越界。
2.重定位寄存器和界地址寄存器
采用重定位寄存器(又称基址寄存器)和界地址寄存器(又称限长寄存器)进行越界检查。重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址。
加入进程1想要访问逻辑地址为80的内存单元,首先逻辑地址会与界地址寄存器中的数据对比,若没有超过这一数据,就认为没有越界,再与重定位寄存器中的数据相加,就可以得到实际要访问的物理地址。
三.内存空间的扩充
1.覆盖技术
早期的计算机内存很小,比如 IBM 推出的第一台PC机最大只支持 1MB 大小的内存。因此经常会出现内存大小不够的情况。后来人们引入了覆盖技术,用来解决“程序大小超过物理内存总和”的问题。
覆盖技术将程序分为多个段(多个模块)常用的段常驻内存,不常用的段在需要时调入内存。
内存中分为一个“固定区”和若干个“覆盖区。需要常驻内存的段放在“固定区”中,调入后就不再调出(除非运行结束)。不常用的段放在“覆盖区”,需要用到时调入内存用不到时调出内存。
若程序X中,模块A会依次调用B和C模块,B模块可能会调用D模块,C模块可能会调用E,F模块。因为A不可能同时访问B,C模块,所以可以将B,C模块设置在同一个覆盖区,同理也可以将D,E,F模块设置在一个覆盖区。
所以,采用覆盖技术可以按照自身逻辑结构,让那些不可能同时被访问的程序段共享同一个覆盖区。
采用覆盖技术中用:8K+10K+12K=30K的大小
必须由程序员声明覆盖结构,操作系统完成自动覆盖。
缺点:对用户不透明,增加了用户编程负担。覆盖技术只用于早期操作系统,现在已经不使用这一技术。
2.交换技术
交换(对换)技术就是指,内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)
内存紧张时,内存就会将某些进程放入外存,而这些进程的PCB会保留在内存中,并插入到挂起队列中。当内存不紧张了,就可以将这些进程调入内存中。
PCB保留在内存中的原因是用于保存该进程在外存中存放的位置,操作系统根据PCB中存放的信息就可以对进程进行管理了。
补充:
暂时换出外存等待的进程状态为挂起状态(挂起态,suspend)
挂起态又可以进一步细分为就绪挂起、阻塞挂起两种状态:
当就绪态被换出内存就称为就绪挂起态,当阻塞态被换出内存就称为阻塞挂起态。
我们之前有讲过高级调度,中级调度和低级调度,那么交换技术就是中级调度使用的一种交换策略,忘记了可以回顾一下:
http://t.csdnimg.cn/rmIN2
中级调度就是用来决定哪个处于挂起状态的进程将被重新调入。
1.应该在外存(磁盘)的什么位置保存被换出的进程?
具有对换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分。
文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式;
对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式。总之,对换区的I/O速度比文件区的更快。
2.什么时候需要交换?
交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。
例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率明显下降,就可以暂停换出。
3.应该换出哪些进程?
①可优先换出阻塞进程;
因为处于就绪态的进程是可以运行的,而处于阻塞态的进程暂时不能运行。
②可换出优先级低的进程;
③为了防止优先级低的进程在被调入内存后很快又被换出,这就有可能导致优先级低的进程饥饿的现象,所以有的系统还会考虑进程在内存的驻留时间。
注:PCB会常驻内存,不会被换出外存。
覆盖技术与交换技术的区别:
1.覆盖技术是在同一个程序或进程中进行的。
2.交换技术是在不同进程(或作业)之间进行的。
3.虚拟存储技术
在传统存储管理方式的基础上引入了交换技术、覆盖技术,使得内存利用率有所提升,并且能从逻辑上扩充内存容量。
但是在传统存储管理中,许多很多暂时用不到的数据也会长期占用内存,导致内存利用率不高。
并且传统存储方案有两个很明显的特征:
一次性:作业必须一次性全部装入内存后才能开始运行。这会造成两个问题:
①作业很大时,不能全部装入内存,导致大作业无法运行;
②当大量作业要求运行时,由于内存无法纳所有作业,因此只有少量作业能运行,导致多道程序并发度下降。
驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行,这就导致了内存中会驻留大量的、暂时用不到的数据,浪费了宝贵的内存资源。
以上传统存储管理方式遇到的问题都可以用虚拟存储技术解决。
首先讲一下局部性原理:
时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)
空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的,由于程序是顺序执行的,所以只要执行了第一条指令,那么与他相邻的第二条指令也有可能在不久之后被接着访问)
•虚拟内存的定义
基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。就是高速缓存技术,使用频繁的数据放到更高速的存储器中。
在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。这就是操作系统虚拟性的一个体现,实际的物理内存大小没有变,只是在逻辑上进行了扩充。
•虚拟内存的特征
多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。
对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。
•实现虚拟内存技术的方法
虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会不方便实现。因此,虚拟内存的实现需要建立在离散分配的内存管理方式基础上。(非连续分配管理方式下面会讲到,建议先看下面),如图,根据不同的非连续分配存储管理方式形成了不同的实现虚拟内存技术的方法:
传统的非连续分配存储管理的方式与虚拟内存的实现方法最主要的区别在于:
对于虚拟内存,需要满足更高的需求,就是:
1.在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
2.若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
由于这两个需求的增加,操作系统需要在基本的非连续分配存储管理中加入请求调页(或请求调段)功能,页面置换(或段置换)功能
请求调页(或请求调段)功能:若需要的页(段)不在内存中,操作系统需要将其调入内存。
页面置换(或段置换)功能:当内存空间不足时,操作系统需要将暂时用不到的分页(分段)暂时调出内存。
3.当页面被访问时,需要修改请求页表(请求分页存储管理中对应的页表)中新增的页表项
(1)请求分页管理技术
之前讲过请求分页存储管理与基本分页存储管理的主要区别在于,请求分页存储管理多了请求调页功能与页面置换功能。
请求分页存储管理与基本分页存储管理的页表机制有何不同?
与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存中存放的位置。
操作系统需要通过某些指标来决定到当内存空间不够时,要实现“页面置换”时,到底换出哪个页面;有的页面没有被修改过,就不用再浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。
相比于基本分页存储管理中的页表:
请求分页存储管理的页表如下所示,称为请求页表
状态位表示是否已调入内存:例如0号页状态位为0,表示0号页没有调入内存。
访问字段可记录最近被访问过几次,或记录上次访问的时间,供置换算法选择换出页面时参考。(例如换出访问次数少的或者是很长时间没有访问的页面)
修改位表示页面调入内存后是否被修改过,没有修改过的页面是不需要写回外存的
外存地址表示各个页面在外存中的存放位置
为了实现请求调页功能,系统中引入了缺页中断机构:
假设此时要访问逻辑地址=(页号,页内偏移量)=(0,1024)
缺页中断机构会根据页表项判断该页面是否在内存当中,若没有在内存中,即状态位为0,那么会产生缺页中断。然后由操作系统的缺页中断处理程序处理中断。中断处理的过程需要I/O操作,把页面从外存调入内存。
所以在等待I/O操作的这一过程中,之前发生缺页的这一进程会阻塞,放入阻塞队列中,调页完成后再将其唤醒,放回就绪队列中。
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。
例如,此时有空闲的块a在内存中,就可以把空闲块分配给缺页的进程,并且将所缺的页面装入该块中,并且修改页表项中对应的数据。
如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
例如,这里换页面2,那么由于2号页面被修改过,就需要写回外存,并且覆盖外存中原来的数据。那么2号页面以前占用的c号块,就可以让出来,给0号页使用了。
修改页表项中对应的数据,最终得:
缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断,由于此中断是能够被修复的,所以属于内中断中的故障。
另外补充一下:
一条指令在执行期间,可能产生多次缺页中断。(如:copy A to B,即将逻辑地址A中的数据复制到逻辑地址B,而A、B属于不同的页面,则有可能产生两次中断)
总结:引入了缺页中断机构后,系统才能实现请求调页的功能。
地址变换机构:
① 首先将页号与页表长度进行对比,检查此时页号是否越界
② 若没有越界,则查询快表中有没有这一页表项,快表命中,可直接得到最终的物理地址,若快表没有命中,则需要查询请求页表,找到对应页表项后,若对应页面未调入内存,即状态位为0。则产生缺页中断,之后由操作系统的缺页中断处理程序进行处理(包括调页和页面置换以及修改对应页表项)。
③ 调入的页面对应的表项会直接加入快表,查询快表,此时命中,则直接得到目标物理地址
注:快表中有的页面一定是在内存中的。若某个页面被换出外存,则快表中的相应表项也要删除否则可能访问错误的页面。
具体步骤如下:
对于上图:
① 只有“写指令”才需要修改“修改位”。并且,一般来说只需修改快表中的数据,只有要将快表项删除时才需要写回内存中的慢表。这样可以减少访存次数。
② 和普通的中断处理一样,缺页中断处理依然需要保留CPU现场。
③ 需要用某种“页面置换算法”来决定一个换出页面。
④ 换入/换出页面都需要启动慢速的I/O操作,可见,如果换入/换出太频繁,会有很大的开销。
⑤页面调入内存后,需要修改慢表,同时也需要将表项复制到快表中。
在具有快表机构的请求分页系统中,访问一个逻辑地址时,若发生缺页,则地址变换步骤是:查快表(未命中)--查慢表(发现未调入内存)--调页(调入的页面对应的表项会直接加入快表)--查快表(命中)--访问目标内存单元
这里需要查两次快表,第一次未命中,将调入的页面对应的表项加入快表,再次查询快表,就可以直接访问目标内存单元。
这里的地址变换与基本分页的区别:
①找到页表项时需要检查页面是否在内存中
②若页面不在内存中,需要请求调页
③若发现内存中没有空闲空间,那么需要通过页面置换算法进行页面置换
④页面调入内存后,需要修改相应的页表项
(2)页面置换算法
页面的换入、换出需要磁盘I/O,会有较大的开销,因此好的页面置换算法应该追求更少的缺页率,也就是减少换入换出内存的次数。
•最佳置换算法
每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。
例:假设系统为某进程分配了三个内存块,并考虑到有一下页面号引用串(会依次访问这些页面):7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
在访问第一个2时,由于此时内存块已满,选择从 0,1,7 中淘汰一页。按最佳置换的规则往后寻找,最后一个出现的页号就是要淘汰的页面,所以最后一个出现的页号为7,所以淘汰7号页面。访问其他页面同理。
接下来访问0号页面,由于0号页面已经在内存中了,所以不需要将页面换出内存。
整个访问过程中,缺页中断发生了9次,即产生了页面的换入和换出。
但是页面置换只发生了6次,在前3个访问页面的过程中,虽然发生了页面调入内存,但是并没有产生页面置换。只有内存块满才会发生页面置换
所以缺页时未必发生页面置换。若还有可用的空闲内存块,就不用进行页面置换。
将缺页中断次数/访问页面次数即可得到缺页率:
9/20=45%
最佳置换算法可以保证最低的缺页率,但实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面。操作系统无法提前预判页面访问序列。因此,最佳置换算法是无法实现的。
•先进先出置换算法
每次选择淘汰的页面是最早进入内存的页面。
所以把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面即可。
队列的最大长度取决于系统为进程分配了多少个内存块。
例:假设系统为某进程分配了三个内存块,并考虑到有以下页面号引用串:
3,2,1,0,3,2,4,3,2,1,0,4首先访问3,2,1队列,并且将页面依次放入队列的队尾
接下来访问0号页面,根据系统中的队列,我们知道3号页面进入内存最早,所以淘汰3号页面,将0号页面放入相应内存块,并且将0号页面放入队尾。访问其他页面同理。
可以观察到,缺页次数为9次
若为进程分配4个内存块,则得到:
分配四个内存块时,缺页次数:10次。分配三个内存块时缺页次数:9次。但是为进程分配的内存块越多,缺页次数应该越少才对,对于这一异常称为:
Belady 异常----当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。
只有 FIFO 算法会产生 Belady 异常。另外,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此,算法性能差。
•最近最久未使用置换算法
每次淘汰的页面是最近最久未使用的页面。
实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面自上次被访问以来所经历的时间t。当需要淘汰一个页面时,选择现有页面中t值最大的,即最近最久未使用的页面。
例:假设系统为某进程分配了四个内存块,并考虑到有以下页面号引用串:1,8,1,7,8,2,7,2,1,8,3,8,2,1, 3,1,7,1,3,7
在访问以下图中的3号页面时,由于内存块已满,必须选择淘汰一个页面。
在手动做题时,若需要淘汰页面,可以逆向检查此时在内存中的几个页面号。在逆向扫描过程中最后一个出现的页号就是要淘汰的页面。例如下图,此时在内存中拥有1,8,7,2页面,出现的顺序依次为8,1,2,所以7号页面是最近最久未被访问的页面。
因此淘汰7号页面。3号页就会放入到7号页以前存在的内存块3中。其他页面访问同理。
该算法的实现需要专门的硬件支持,虽然算法性能好,但是实现困难,开销大。
•时钟置换算法
最佳置换算法性能最好,但无法实现;
先进先出置换算法实现简单,但算法性能差;
最近最久未使用置换算法性能好,是最接近OPT算法性能的,但是实现起来需要专门的硬件支持,算法开销大。
时钟置换算法是一种性能和开销较均衡的算法,又称CLOCK算法,或最近未用算法(NRU,Not Recently Used)
简单的CLOCK 算法实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位置为1。当需要淘汰一个页面时,只需检査页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描 (第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK算法选择一个淘汰页面最多会经过两轮扫描)
例:假设系统为某进程分配了五个内存块,并考虑到有以下页面号引用串:1,3,4, 2,5,6, 3,4,7
由于内存块为5个,所以1,3,4,2,5这5个页面会通过链接指针的方式,形成循环队列:
接下来会访问6号页面,但是由于内存块满,所以开始从循环队列的队首开始扫描,尝试找到访问位为0的页面,并且被扫描的页面需要将访问位由1改为0,所以第一轮扫描后,所有的位由1变为0
进行第二轮访问时,由于1号页面访问位为0,则淘汰1号页面,所以:
①6号页面替换1号页面
②6号页访问位置为1
③指针指向下一个页面,其他页面同理。
接下来访问3,4号页,将其访问为从0变为1
接下来访问7号页,由于7号页没有在内存中,所以从当前指针位置开始依次扫描,找到第一个访问位为0的页面,并且扫描过的页面需要由1变为0。
所以3号和4号页面扫描后,访问位由1变为0。
扫描到2号页面时,由于2号页面访问位为0,所以2号页面被淘汰,将7号页面放入此内存块中,并且将其访问位置为1,指针指向下个页面。
•改进型的时钟置换算法
简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。所以如果能优先淘汰没有修改过的页面,就可以减少I/O操作的次数。
因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/O操作。这就是改进型的时钟置换算法的思想。修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。
为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,
且被修改过。
算法规则:将所有可能被置换的页面排成一个循环队列
访问位为0,若修改位为0,即页面没有被修改过则优先淘汰,即(0,0)
所以淘汰顺序为(0,0)(0,1)
访问位为1,就是最近被访问过,不考虑淘汰,即(1,0)(1,1)
第一轮:从当前位置开始扫描到第一个(0,0)的用于替换。本轮扫描不修改任何标志位
第二轮:若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
第三轮:若第二轮扫描失败,则重新扫描,查找第一个(0,0)的用于替换。本轮扫描不修改任何标志位
第四轮:若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。
由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描定会有一个帧被选中,因此改进型CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描。
而简单型CLOCK置换算法最多只会进行两轮扫描。
通俗来讲,第一轮找(9,0),第二轮降低要求找(0,1),并且修改访问位为0,所以第三轮只有(0,1)或(0,0),第三轮试图找最先淘汰的(0,0),第四轮若没有找到(0,0),只能找第2位(次位)可以被淘汰的(0,1)
假设系统为进程分配了5个内存块,5个内存块被占满后,各个页面会用链接的方式形成循环队列:
只需要经过1轮扫描的情况:
第一轮扫描不需要修改任何标志位,只需要找到第一个(0,0)用于替换。就是下一个需要访问的页面替换掉此内存块中的页面。
进行两轮扫描:
假设此时内存块中页面状态如下:
第一轮扫描都不满足(0,0)这一状态,回到(1,1)
第二轮扫描会找第一个为(0,1)的页面,并且将访问过的帧访问位设为0。
,
进行3轮扫描:
第一轮找不到(0,0)这样的页面,所以进入第二轮扫描
第二轮扫描中,试图寻找(0,1),并且将访问过的页面的访问位由1变为0
没有找到(0,1),进行第三轮扫描,找到第一个(0,0)的页面,将此页面进行替换,即淘汰此页面
进行4轮扫描:
第一轮扫描需要找到(0,0)状态页面,不改变标志位
没有(0,0)页面,进行第二轮扫描,试图寻找(0,1),并且将访问过的页面的访问位由1变为0
没有找到(0,1),进行第三轮扫描,寻找(0,0)页面,并且不修改任何标志位
第三轮扫描无(0,0)页面,所以进行第四轮扫描,寻找(0,1)页面,并且替换该页面。
第一优先级:对应第一轮,最近没访问,且没修改的页面
第二优先级:对应第二轮,最近没访问,但修改过的页面第三优先级:对应第三轮,最近访问过,但没修改的页面,也就访问位以前是1,修改位以前是0的页面(1,0),由于前一轮中,将所有扫描过的页面的访问位由1变为0,所以第三轮实际要找的是(0,0)
第四优先级:对应第四轮,最近访问过且修改过的页面(由于第2轮已经将所有扫描过的页面的访问位由1变为0,第三轮没有找到(0,0),所以到了第四轮找(0,1))。
这一算法(改进型CLOCK)开销小,性能较好。
四.内存空间的分配与回收
1.连续分配管理方式
连续分配是指为用户进程分配的必须是一个连续的内存空间。
(1)单一连续分配
在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。
内存中只能有一道用户程序,用户程序独占整个用户区空间。
优点:实现简单;无外部碎片;可以采用覆盖技术扩充内存;不一定需要采取内存保护(例如早期的PC操作系统 MS-DOS)
缺点:只能用于单用户、单任务的操作系统中;有内部碎片(分配给某进程的内存区域中,如果有些部分没有用上,就是"内部碎片");存储器利用率极低。
补充:
内部碎片是指分配给某进程的内存区域中,有些部分没有用上。
外部碎片是指内存中的某些空闲分区由于太小而难以利用。如果内存中空闲空间的总和本来可以满足某进程的要求但由于进程需要的是一整块连续的内存空间,因此这些“碎片”不能满足进程的需求。可以通过紧凑(拼凑,Compaction)技术来解决外部碎片。如下图,采用紧凑技术,就可以运行进程1。
紧凑技术需要修改进程的起始地址,而起始地址存放在进程的PCB中,当进程上CPU运行时,会将进程的起始地址放到重定位寄存器(基址寄存器)中。
(2)固定分区分配
20世纪60年代出现了支持多道程序的系统,为了能在内存中装入多道程序,且这些程序之间又不会相互干扰,于是将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。
① 分区大小相等
分区大小相等的缺点在于缺乏灵活性,若是一个小进程,但是由于分区大小相等,所以会占用相对较大的分区。若有一个较大的进程,系统分区的大小不能满足这一进程,那么这一进程就不能被装入系统中(或者说只能采用覆盖技术,在逻辑上拓展分区的大小)。
但是很适合用于用一台计算机控制多个相同对象的场合。
② 分区大小不等
分区大小不等是增加了灵活性,可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分(比如:划分多个小分区、适量中等分区、少量大分区)
操作系统应如何记录各个分区分配的情况:
操作系统会建立一个数据结构一分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)。
当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为“已分配”。
用数据结构的数组(或链表)即可表示这个表。
优点:实现简单,无外部碎片。
缺点:
•当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能;
•会产生内部碎片,内存利用率低。
(3)动态分区分配
动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。(例如:假设某计算机内存大小为64MB,系统区8MB,用户区共56MB..)
注:动态分区的分配应该使用动态重定位的方式
系统要用什么样的数据结构记录内存使用情况?
1.空闲分区表
每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息。若没有在空闲分区表中的分区,那么就是已经被使用的分区。
2.空闲分区链
每个分区的起始部分和末尾部分分别设置前向指针和后向指针(即指向前面空闲分区的指针和指向后面空闲分区的指针)。起始部分处还可记录分区大小等信息
当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?
使用动态分区分配算法:
1.首次适应算法
每次都从低地址开始查找,找到第一个能满足大小的空闲分区。
如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
若进程5想要进入内存运行,那么使用首次适应算法,分配情况如下:
2.最佳适应算法
由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区即,优先使用更小的空闲区。
如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区
表),找到大小能满足要求的第一个空闲分区。空闲分区由小到大依次递增链接。
若进程6(9MB)想要进入内存运行,那么就需要找到大小能满足要求的第一个空闲分区,即10MB的空闲分区。
最后空闲分区链也会做出改变,重新排列
缺点:每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片。
3.最坏适应算法
又称最大适应算法(Largest Fit)。为了解决最佳适应算法的问题--即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
注:因为是递减链接,所以空闲分区中的第一个分区肯定是满足要求的,否则之后的分区更加满足不了分区大小的要求。
如图,空闲分区链递减排列:
若此时进程5(3MB)进入内存运行,则选择第一个空闲分区,并且修改相应的空闲分区链:
注:若修改后空闲分区的大小不是按照递减的方式排列,那么就需要重新进行排列
缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。
4.邻近适应算法
首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。
如何实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
运行流程如下:
若进程5想要进入内存运行,那么从链头开始,6MB的分区满足条件,则进入6MB的分区
因为是按照地址递增的顺序进行排列,所以顺序是不用更换的。
若现在有进程6(5MB)进入内存空间,只需要从上一次结束的位置来时检索即可,就是1
可以观察到,若使用首次适应算法,那么会进行3次比较,而使用邻近适应算法,只需要进行2次比较。
但是,邻近适应算法也有不足:
首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳适应算法的优点)。
邻近适应算法的规则可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用(最大适应算法的缺点)。
综合来看,四种算法中,首次适应算法的效果反而更好。
总结:
如何进行分区的分配与回收操作?假设系统采用的数据结构是“空闲分区表,该如何分配?
分区的分配:
若将进程5(4MB)放入分区号为1的分区,那么表项就会做如下改变
若进程5(4MB)放入分区号为3的分区,分区3大小刚好为4MB,那么分区表就会将此分区删除。
分区的回收:
① 若进程4运行完毕需要进行回收,回收区的后面有一个相邻的空闲分区,那么就合并两个相邻的空闲分区。
那么需要改变分区的大小与起始地址:
② 若回收区前面有一个相邻的空闲分区,那么需要将两个空闲分区合并为一个。
那么不需要改变起始地址,只需要改变分区大小:
③ 若回收区的前、后各有一个相邻的空闲分区,需要将3个空闲分区合并为一个
空闲分区表修改如下:
④ 回收区的前、后都没有相邻的空闲分区
就需要新增表项:
注:各表项的顺序不一定按照地址递增顺序排列,具体的排列方式需要依据动态分区分配算法来确定。
动态分区分配的特点:动态分区分配没有内部碎片,但是有外部碎片。
2.非连续分配管理方式
连续分配指的是为用户进程分配的必须是一个连续的内存空间,而非连续分配指的是为用户进程分配的可以是一些分散的内存空间。
(1)基本分页存储管理
•页框和页
将内存空间分为一个个大小相等的分区(比如:每个分区4KB),每个分区就是一个“页框”(页框=页帧=内存块=物理块=物理页面)。每个页框有一个编号,即“页框号”(页框号=页帧号=内存块号=物理块号=物理页号),页框号从0开始。
将进程的逻辑地址空间也分为与页框大小相等的一个个部分:每个部分称为一个“页”或“页面”。每个页面也有一个编号,即“页号”,页号也是从0开始。操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一 一对应的关系。
•页表
操作系统会建立一张页表,记录页面和页框的一 一对应关系,即知道每个进程的每个页面在内存中存放的位置。页表一般存放在PCB(进程控制块)中。
1.一个进程对应一张页表
2.进程的每个页面对应一个页表项
3.每个页表项由“页号”和“块号”组成
注意:页表记录的只是内存块号,而不是内存块的起始地址。J号内存块的起始地址 =J*内存块大小
4.页表记录进程页面和实际存放的内存块之间的映射关系
每个页表项占多少字节?
假设某系统物理内存大小为4GB,页面大小为4KB,则每个页表项至少应该为多少字节?
因为页面和内存块大小相等,所以
内存块大小=页面大小=4KB=2^12B
4GB的北村总共会被分为2^32/2^12=2^20个内存块
内存块号的范围应该是0~2^20-1
内存块号至少要用20bit来表示
那么以字节为单位分配的话,即至少要用3B来表示块号(3*8=24bit)
页号需要占多少字节?
页表项连续存放,因此页号可以是隐含的,不占存储空间的(类比数组的下标)
例如:
假设页表中的各页表项从内存地址为x的地方开始连续存放..…如何找到页号为i的页表项?
i号页表项的存放地址=X+3*i
由于页号是隐含的,因此每个页表项占3B,存储整个页表至少需要 3*(n+1)B。
所以一个页表项逻辑上需要存放页号和块号两个信息,实际物理上只需要存放块号这一信息
如何实现地址转换?
在连续存放中,CPU将重定位寄存器中的起始位置与某内存单元存储的逻辑地址相加得到最终目标地址,这一逻辑地址相当于"偏移量"
对于非连续存放而言:
虽然进程的各个页面是离散存放的,但是页面内部是连续存放的
如果要访问逻辑地址 A,则
①首先确定逻辑地址A对应的"页号"P
②操作系统再根据页号查询页表,找到页号对应的块号,得出P号页面在内存中的起始地址,即内存块大小*块号
③确定逻辑地址A的"页内偏移量"W
④那么逻辑地址A对应的物理地址=P号页面在内存中的起始地址+页内偏移量W
步骤①中,如何确定页号和页内偏移量?
页号=110/50=2
页内偏移量=110%50=10
根据上图,可以很清楚的知道逻辑地址对应的页号为2号页面,页内偏移量为10。
页号=逻辑地址/页面长度(取除法的整数部分)
页内偏移量=逻辑地址%页面长度(取除法的余数部分)
逻辑地址可以拆分为(页号,页内偏移量)
通过页号查询页表,可知页面在内存中的起始地址
页面在内存中的起始地址+页内偏移量=实际的物理地址注:在计算机内部,地址是用二进制表示的如果页面大小 刚好是2的整数幂,则计算机硬件可以很快速的把逻辑地址拆分成(页号,页内偏移量)
可以观察到,在32位2进制中,前面的红色部分(20位)就是页号,后面的黑色部分(12位)就是页内偏移量,具体的例子如下:
结论:如果每个页面大小为 2^K B,用二进制数表示逻辑地址则末尾K位即为页内偏移量,其余部分就是页号
逻辑地址结构:
所以,通过页面大小可推出页内偏移量,通过页内偏移量也可以推出页面大小,再通过地址位数,可以得到逻辑地址的结构。
若某些题目中页面大小不是2的整数幂,那么只能用常规的方法:
若题目中页面大小是2的整数幂,那么如果知道页面大小就可以推出页号,例如用 32 个二进制位表示逻辑地址,页面大小为
4KB=2^12B,所以页内偏移量占12位,页号占20位,0号页(00000000000000000000)
1号页(00000000000000000001)……
以上是逻辑地址的拆分,其实,若页面大小为2的整数幂,对于物理地址的计算也很方便:
我们之前知道J号内存块的起始地址=J*内存块大小,但是,如果页面大小是2的整数幂,那么起始物理地址就是等于内存块号,如下图例子,后面页内偏移量占12位,前面内存块号占20位,所以3号内存块的起始地址就为前面的20位(00000000000000000011),以及后面的12位(000000000000)结合而成
例如:
假设通过查询页表得知1号页面存放的内存块号是9(1001),则
则逻辑地址4097对应的物理地址=页面在内存中存放的起始地址+页内偏移量
(页内偏移量=4097%4096=1),最后得到:
结论:如果页面大小刚好是2的整数幂,则只需把页表中记录的物理块号拼接上页内偏移量就能得到对应的物理地址。而如果不是2的整数幂,那么页面在内存中存放的起始地址必须通过
内存块大小*块号计算
总结:页面大小刚好是2的整数幂有什么好处?
①逻辑地址的拆分更加迅速,如果每个页面大小为 2^K B,用二进制数表示逻辑地址,则末尾K位即为页内偏移量,其余部分就是页号。因此,如果让每个页面的大小为2的整数幂,计算机硬件就可以很方便地得出一个逻辑地址对应的页号和页内偏移量,而无需进行除法运算,从而提升了运行速度。
②物理地址的计算更加迅速,根据逻辑地址得到页号,根据页号查询页表从而找到页面存放的内存块号,将二进制表示的内存块号和页内偏移量拼接起来,就可以得到最终的物理地址。
•基本地址变换机构
基本地址变换机构可以借助进程的页表将逻辑地址转换为物理地址。
通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址F和页表长度M。进程未执行时,页表的始址和页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。
设页面大小为L,逻辑地址A到物理地址E的变换过程如下:
注:页面大小是2的整数幂
若某进程被调度,则需要上处理机运行,那么进程切换相关的内核程序就会负责恢复进程运行环境,进程运行环境相关的信息本来是保存在内存的系统区的PCB中的,之后,内核程序会把这些数据放到一系列寄存器中,其中包括页表寄存器。
同时程序计数器PC中的数据也需要恢复,即指向这一进程下一条指令的逻辑地址A
那么CPU怎么怎么通过逻辑地址找到相应的物理地址?
①计算页号P和页内偏移量W(如果用十进制数手算,则P=A/L,W=A%L;但是在计算机实际运行时,逻辑地址结构是固定不变的,因此计算机硬件可以更快地得到二进制表示的页号、页内偏移量)
②比较页号P和页表长度M,若PM,则产生越界中断,否则继续执行。(注意:页号是从0开始的,而页表长度至少是1,因此 P= M 时也会越界)
③若不越界,页表中页号P对应的页表项地址=页表起始地址F+页号P*页表项长度,取出该页表项内容b,即为内存块号。
注意区分页表项长度、页表长度、页面大小的区别。页表长度指的是这个页表中总共有几个页表项,即总共有几个页;页表项长度指的是每个页表项占多大的存储空间;页面大小指的是一个页面占多大的存储空间。
④计算E=b*L+W,用得到的物理地址E去访存。(如果内存块号、页面偏移量是用二进制表示的,那么把二者拼接起来就是最终的物理地址了)
可以看到以上操作总共需要访问两次内存:
第一次访问:查页表
第二次访问:访问目标内存单元
例:若页面大小L为1K字节,页号2对应的内存块号b=8,将逻辑地址A=2500转换为物理地址E。等价描述:某系统按字节寻址,逻辑地址结构中,页内偏移量占10位(2^10B=1KB),页号2对应的内存块号b=8,将逻辑地址 A=2500 转换为物理地址E。
在分页存储管理(页式管理)的系统中,只要确定了每个页面的大小,逻辑地址结构就确定了。因此,页式管理中地址是一维的。即,只要给出一个逻辑地址,系统就可以自动地算出页号、页内偏移量两个部分,并不需要显式地告诉系统这个逻辑地址中页内偏移量占多少位。
每个页表项的长度是相同的,页号是“隐含”的
例如:假设某系统物理内存大小为4GB,页面大小为4KB,的内存总共会被分为2^32/2^12=2^20 个内存块,因此内存块号的范围应该是0~2^20-1
因此至少要20个二进制位才能表示这么多的内存块号,因此至少要3个字节才够(每个字节8个二进制位,3个字节共24个二进制位)各页表项会按顺序连续地存放在内存中如果该页表在内存中存放的起始地址为X,则M号页对应的页表项是存放在内存地址为X+3*M
一个页面为4KB,则每个页框可以存放4096/3=1365个页表项,但是这个页框会剩余4096%3=1B页内碎片
因此,1365号页表项存放的地址为X+3*1365+1(因为不能跨页框存储)
如果每个页表项占4字节,则每个页框刚好可存放1024个页表项
1024号页表项虽然是存放在下一个页框中的,但是它的地址依然可以用X+4*1024得出结论:理论上,页表项长度为3B即可表示内存块号的范围,但是,为了方便页表的查询,常常会让一个页表项占更多的字节,使得每个页面恰好可以装得下整数个页表项。
•具有快表的地址变换机构
快表,又称联想寄存器(TLB,translation lookaside buffer),是一种访问速度比内存快很多的高速缓存(TLB不是内存),用来存放最近访问的页表项的副本,可以加速地址变换的速度。与此对应,内存中的页表常称为慢表。
注:TLB 和 普通 Cache 的区别:
TLB 中只有页表项的副本,而普通 Cache 中可能会有其他各种数据的副本
若在告诉缓存中已经找到了想要的数据,那么就不必访问内存了,并且CPU从Cache高速缓存存取数据的速度比内存快很多,所以使用此方法,CPU在进行地址变换时,查询页表的速度就快的多。
能否把整个页表都放在TLB中?不行
与高速缓存一样,他的容量小,且更贵。
快表是如何工作的?
假设某进程执行过程中要依次访问(0,0)、(0,4)、(0,8)(页号,块号)这几个逻辑地址。若访问TLB只需1us,而访问内存需要100 us。
注:当进程切换时,快表的内容需要被清除。
①首先将进程的页号与页表寄存器中的页表长度对比,若页号大于页表长度则越界异常。
②若没有越界,则查询快表,但是由于进程刚上处理机运行,因此快表中的内容为空,快表中找不到页号为0的页表项,因此快表没有命中
③若快表没有命中,那么就会访问慢表,通过页表始址+页号*页表项长度,得到内存块号,再根据内存块号和页内偏移量即可得到目标物理地址。
④因为快表中没有该页表项,所以会从慢表中复制一份,放到快表中。接着通过内存块号与页内偏移量拼接得到页号为0,页内偏移量为0的目标物理地址。
⑤接下来要访问的是页号为0,页内偏移量为4的地址。
⑥经过比较页号和页表长度,发现没有越界,则查询快表,此时快表中有相应的页表项。那么操作系统就能通过快表的相应页号,直接查询内存块号
⑦再通过内存块号和页内偏移量就可以得到目标物理地址
因为地址变换就需要查询慢表,而访问慢表需要100us的时间,若引入快表,快表中有相应的页表项,那么地址变换就只需要1us的时间。
注:快表中存放的是页表的一部分副本,因为快表造价高,容量小,不足以放下所有副本。
总结:
① CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
② 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此若快表命中,则访问某个逻辑地址仅需一次访存即可。
③ 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)
由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间。
因为局部性原理,一般来说快表的命中率可以达到 90%以上。补充:
时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。
空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的)
上面介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。
由于局部性原理,可能连续很多次查到的都是同一个页表项。
以上的1+100是快表中有相应页表项的时间,先访问快表(1us),若快表中有相应的页表项,则直接访问内存(100us)
1+100+100是快表中没有相应页表项的情况,即先访问快表(1us),没有相应的页表项,则先访问内存(慢表)(100us),计算得到内存块号后,在访问内存(100us)
若未采用快表机制,则访问一个逻辑地址需要100+100=200us注:若慢表和快表同时查找,那么从(1+100+100)*0.1到(100+100)*0.1
经过下图可知,若慢表和快表同时查询,当查询快表使用1us的时间时,系统已经花费1us的时间查询慢表了,所以查询慢表的时间-1。
•两级页表
单极页表存在的问题:
某计算机系统按字节寻址,支持32位的逻辑地址,采用分页存储管理,页面大小为4KB,页表项长度为 4B。
4KB=2^12B,因此页内地址要用12位表示,剩余20位表示页号。
因此,该系统中用户进程最多有2^20页。相应的,一个进程的页表中,最多会2^20=1M=1,048,576个页表项,所以一个页表最大需要2^20*4B=2^22B,共需要 2^22/2^12=2^10个页框存储该页表。
根据页号查询页表的方法:
K号页对应的页表项存放位置=页表始址+K*4
要在所有的页表项都连续存放的基础上才能用这种方法找到页表项
① 因此:需要专门给进程分配2^10=1024 个连续的页框来存放它的页表。页表很大,需要占用很多个连续的页框,并且这样丧失了非连续分配管理方式的优势。
② 根据局部性原理可知,很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存。
对于问题①的解决方法:
我们可将长长的页表进行分组,使每个内存块刚好可以放入一个分组(比如上个例子中,页面大小4KB,每个页表项 4B,每个页面可存放 1K个页表项,因此每1K个连续的页表项为一组,每组刚好占一个内存块,再讲各组离散地放到各个内存块中)
为了在各个分组中找到各分组的先后顺序,需要再建立一个页表,称为页目录表,或称外层页表,或称顶层页表。
两级页表的地址结构:
32位逻辑地址空间,页表项大小为4B,页面大小为4KB,则页内地址占12位
单级页表结构如下:
进程最多有 2^20 个页面,用 20 位二进制刚好可以表示 0~2^20-1 个页号。
由于页面大小=内存块大小,所以每个内存块就可存放 4K/4 =1K=2^10=1024个页表项
注:一个页号对应一个页表项,这里的相除使想得到内存块中可存放的页表项数。
我们将页表分为1024个页表,所以原来页表中页号为1024的页表项,变为了下面红框中的页表项。
两级页表的结构如下:
上图的一级页号的10个2进制位用于表示页表数量,二级页号的10个2进制位用于表示每个页表中的页表项数量。
为区分各个小页表(二级页表)的顺序而设置的页表即页目录表
页目录表记录的是(页表+内存块号),例如0号页表存放在3号内存块中
如何对两级页表结构的逻辑地址进行地址变换?
例:将逻辑地址(0000000000,0000000001,111111111111)转换为物理地址(用十进制表示)。
① 按照地址结构将逻辑地址拆分成三部分
② 从PCB中读出页目录表始址,再根据一级页号查页目录表,找到下一级页表在内存中的存放位置。例如,0号页表放在内存中的3号内存块中
③再根据3号内存块存放的二级页表,得到最终需要访问的地址
由于二级页号=0000000001,所以对应的块号是4,访问4号内存块
④结合页内偏移量得到目标物理地址,即4号内存块号结合页内偏移量:
内存块大小*内存块号+页内偏移量
对于②问题:进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存。
可以在需要访问页面时才把页面调入内存(虚拟存储技术)。可以在页表项中增加一个标志位,用于表示该页面是否已经调入内存
若想访问的页面不在内存中,则产生缺页中断,然后将目标页面从外存调入内存。
注:缺页中断指的是正在执行的某个指令,想访问暂时没有调入内存的页面产生的,所以此中断信号与当前执行的指令有关,属于内中断。
对于多级页表需要注意:
1.若采用多级页表机制,则各级页表的大小不能超过一个页面。
例:某系统按字节编址,采用40位逻辑地址,页面大小为4KB,页表项大小为4B,假设采用纯页式存储,则要采用()级页表,页内偏移量为()位?
由于页面大小是4KB,而系统又是按字节存储的,所以页内偏移量为4KB=2^12B
页号=40-12=28位
页面大小=2^12B,页表项大小=4B,页面大小=内存块大小,则每个内存块可存放2^12/4=2^10个页表项
因此各级页表最多包含 2^10个页表项,需要 10 位二进制位才能映射到 2^10个页表项,因此每一级的页表对应页号应为10位。总共28位的页号至少要分为三级
如果只分为两级页表,则一级页号占 18 位,也就是说页目录表中最多可能有 2^18 个页表项,显然,一个页面是放不下这么多页表项的。
2.两级页表的访存次数分析(假设没有快表机构)
第一次访存:访问内存中的页目录表
第二次访存:访问内存中的二级页表
第三次访存:访问目标内存单元而单级页表只需要进行两次访存,第一次访问页表,第二次访问目标内存单元
得出结论访问使用n级页表,访存次数为n+1次
所以两级页表在空间利用率上升的同时,牺牲了访存的时间,即访存的时间比单级页表长。
(2)基本分段存储管理
基本分段存储管理与与基本分页存储管理最大的区别就是离散分配时所分配地址空间的基本单位不同。
•分段
进程的地址空间会按照程序自身的逻辑关系划分为若于个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。例如,0号段占据的是从80K开始的连续的7KB的地址空间。
由于是按逻辑功能模块划分,用户编程更方便,程序的可读性更高。例如,用户可用汇编语言(低级语言)写这样两条指令。
注:在用户编程时使用的是各个段名来操作各个段,但是在CPU具体执行时,使用的是段号这个参数,所以在编译程序中会将段名转换为段号。CPU在执行各个指令时是根据段号来区分各个段的。
分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。如:
段号的位数决定了每个进程最多可以分几个段,段内地址位数决定了每个段的最大长度是多少
在上述例子中,若系统是按字节寻址的,则段号占16位,因此在该系统中,每个进程最多有2^16=64K个段。
段内地址占16位,因此每个段的最大长度是 2^16=64KB
写程序时使用的段名 [D]、[X] 会被编译程序翻译成对应段号。<A>单元、<B>单元会被编译程序翻译成段内地址。
•段表
程序分多个段,各段离散地装入内存,为了保证程序能正常运行,就必须能从物理内存中找到各个逻辑段的存放位置。为此,需为每个进程建立一张段映射表,简称“段表”。如下图所示,各个段由段号,段长和段基址(段在内存中的起始位置)组成。
与分页存储管理的页表中很不一样的是,这里记录了段长,因为段长是不同的,而分页大小是相同的。
各个段表项的长度是相同的。
例如:某系统按字节寻址,采用分段存储管理,逻辑地址结构为(段号16位,段内地址16位),因此用16位即可表示最大段长。物理内存大小为4GB(可用32位表示整个物理内存地址空间,所以用32位即可表示最大基址)。因此,可以让每个段表项占16+32=48位,即6B(操作系统可以规定每个段表项就是6个字节,前2个字节表示的是段长,后4个字节表示的是基址)。由于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为M,则K号段对应的段表项存放的地址为M+K*6。
•地址变换
地址变换的过程如下:
经过编译程序编译后,形成等价的机器指令:“取出段号为2,段内地址为1024的内存单元中的内容,放到寄存器1中”
机器指令中的逻辑地址用二进制表示:0000000000000010 0000000100000000
前面16位表示段号,后面16位表示段内地址。
①一个进程上处理机运行之前,需要将进程切换相关的内核程序负责恢复进程运行环境,其中就包括很重要的硬件寄存器---段表寄存器,用于存放某进程的段表起始地址与段表长度。
所以段表的起始地址与段表长度在没有上处理机运行时是存放在进程的PCB中,当进程上处理机运行时,这两个信息会被放在段表寄存器中。当知道段表基址之后,就能知道段表存放的具体位置。
②系统根据逻辑地址A得到段号S和段内地址W,例如这里段号为2,段内地址为1024
③将段号与段表长度进行对比,判断段号是否越界若SM,则产生越界中断,否则继续执行
注:段表长度至少是1,而段号是从0开始的。
④若没有越界,查询段表,找到对应的段表项,段表项的存放地址为F+S*段表项长度
⑤找到对应的段表项后,系统会检查段内地址是否超过段长。若WC,则产生越界中断,否则继续执行。由于这里段长为6K,段内地址为1024,即1K,所以不会产生越界中断。
⑥知道了段表项在内存中存放的位置,那么我们将段基址与段内地址相加,就可以得到实际目标物理地址。接着就能访问目标内存单元了,由于基址是40K,段内地址为1024,所以
40K+1024就是目标内存单元。
由于分页存储管理中,每个页面大小相同,而每个段长不同,所以需要将段长与段内偏移量进行检查。
•分段和分页管理的对比
1.页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。
段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
2.页的大小固定且由系统决定。段的长度却不固定,决定于用编写的程序。
3.分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。
例如下图,用户用助记符A,来表示页面中的某个内存单元。
分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。例如下图,段名为D,段内地址是A。
4.分段比分页更容易实现信息的共享和保护。假设某生产者进程被分为3个段,1号功能段用来判断缓冲区此时是否可访问允许所有生产者、消费者进程共享访问
如何实现共享?只需让各进程的段表项指向同一个段即可。
注:不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)
如果采用"分页"的方式,如果让消费者进程的某个页表项指向这个页面,显然不合理,因为这个页面中的橙色部分是不允许共享的,只有绿色部分可以(也就是只有1号段可以共享)
由于页面不是按逻辑模块划分的。这就很难实现共享。
所以对于分段而言,运行共享的段标记为允许其他进程访问,其他段标记为不允许被其他进程访问即可。
对于分页而言,1、2号页面只有一部分允许其他进程访问,因此很难用页表实现信息保护。
访问一个逻辑地址需要几次访存?
分页(单级页表):第一次访存--查内存中的页表,第二次访存--访问目标内存单元。总共两次
访存
分段:第一次访存---查内存中的段表,第二次访存--访问目标内存单元。总共两次访存。与分页系统类似,分段系统中也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少一次访问,加快地址变换速度。
(3)段页式存储管理
•分页和分段的优缺点
如下图所示,若一个分段为20MB的空间想要进入内存,则无法实现,虽然以下内存空间大小总和为20MB,但是这是不连续的空间。分段管理中产生的外部碎片也可以用“紧凑”来解决,只是需要付出较大的时间代价。
•段页式管理的优点
在段页式管理中就结合了分段管理与分页管理的优点:
如下图所示,将进程按逻辑模块分段,再将各段分页(如每个页面4KB)。
再将内存空间分为大小相同的内存块/页框/页帧/物理块,进程的各页面分别装入各内存块中。
段页式系统的逻辑地址结构由段号、页号、页内地址(页内偏移量)组成。如:
段号的位数决定了每个进程最多可以分几个段
页号位数决定了每个段最大有多少页
页内偏移量决定了页面大小、内存块大小是多少
在上述例子中,若系统是按字节寻址的,则段号占16位,因此在该系统中,每个进程最多有2^16=64K个段
页号占4位,因此每个段最多有2^4=16页
页内偏移量占12位,因此每个页面\每个内存块大小为2^12=4096=4KB
注:“分段”对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。
所以逻辑地址结构由段号、页号、页内地址(页内偏移量)组成。但是编程时只需要显式的给出段号和段内地址,之后系统会自动地将段内地址拆分为页号和页内偏移量。
因此段页式管理的地址结构是二维的。
•段表、页表
一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始每个段对应地址)组成。每个段表项长度相等,段号是隐含的。
根据块号可以算出页表存放的内存地址,如下图,我们可以知道0号段对应的页表存放块号为1
于是从1号块中就可以读出0号段对应的页表,0号段长度为7KB,而每个页面大小为4KB,所以会被分为两个页面
这两个页面就会依次对应页表当中的一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。
我们需要知道:
1.段页式管理中段表的格式与分段式管理中段表的格式不同:
分段式管理中记录的是段号,段表长度和段在内存中的起始地址,而段页式存储记录的是段号,页表长度,以及页表存放块号。
2.对于页表而言,段页式的页表格式与分页式管理的页表格式相同,都是记录了页号与内存块号的映射关系。
3.一个进程只会对应一个段表,但是每个段会对应一个页表,所以一个段表会对应多个页表。
•逻辑地址转为物理地址
①一个进程上处理机运行之前,需要将进程切换相关的内核程序负责恢复进程运行环境,其中就包括很重要的硬件寄存器---段表寄存器,用于存放某进程的段表起始地址与段表长度。
所以段表的起始地址与段表长度在没有上处理机运行时是存放在进程的PCB中,当进程上处理机运行时,这两个信息会被放在段表寄存器中。当知道段表基址之后,就能知道段表存放的具体位置。
②根据逻辑地址得到段号、页号与页内偏移量
③将段号与段表长度进行对比,判断段号是否越界,若SM,则产生越界中断,否则继续执行
④ 查询段表,找到对应的段表项,段表项的存放地址为F+S*段表项长度
由于各个段长不相等,各个段分页后,页面数量可能不同,所以要检查页号是否越界,若页号页表长度,则发生越界中断,否则继续执行
⑤根据页表存放块号、页号查询页表,找到对应页表项。找到页表项后就可以知道这个页面在内存中存放的位置
⑥ 根据内存块号、页内偏移量得到最终的物理地址,就是将这两个数据的2进制进行拼接。
再根据这一物理地址进行访存即可。
由上图我们可以知道,总共需要3次访存:第一次--查段表、第二次--查页表、第三次--访问目标单元。
当然,也可引入快表机构,用段号和页号作为查询快表的关键字。若快表命中,就不需要访问段表和页表了,也就是仅需一次访存
总结不易,若有错误,请佬们不吝赐教!💖💖💖