目录
一、存储器的层次结构
存储器管理的主要对象是内存,这也是本章重点,对外存的管理在文件管理那一章讲解。
多级存储器结构:
主存储器与寄存器:
- 主存储器:可执行存储器
- 寄存器:访问速度最快。
高速缓存和磁盘缓存:
- 高速缓存:访问速度快于主存储器
- 磁盘缓存:利用主存中的存储空间。
二、程序的装入和链接
用户程序要在系统中运行,必须先将它装入内存,然后才能将其转变为一个可以执行的程序,通常要经过以下几个步骤:
1、编译:由编译程序将用户源代码编译成若干个目标模块
2、链接:由链接程序将编译后形成的一组目标模块,以及它们所需要的库函数链接在一起,形成一个完整的装入模块
3、装入:由装入程序将装入模块装入内存
2.1 逻辑地址和物理地址
在进行下面的讲解前,一定要注意区分逻辑地址和物理地址,它们相互对应,但并不相等,它们之间还需要经过地址映射的过程。
逻辑地址:是指程序在运行过程中使用的地址,也称为虚拟地址。它是由CPU生成的,用于访问内存中的数据。它也是程序员编写程序时所采用的一种地址,这些地址有可能会超过物理内存可用的范围。
因此,在内存管理中,操作系统根据自己的算法,将逻辑地址转换为物理地址,使得程序能够正常地读取和写入内存中的数据。
物理地址:是指内存中实际的地址,也称为实地址(Real Address。它是CPU直接访问的硬件设备中的内存单元地址。当CPU访问这样的地址时,它可以直接引用到内存中的数据。
将一个装入模块装入内存时,有三种方式:
2.2 绝对装入方式
在编译时,如果知道程序驻留在内存的什么位置,那么编译程序将产生绝对地址的目标代码。
装入模块装入内存后,程序中的逻辑地址与实际内存地址完全相同,不须对程序和数据的地址进行修改。
程序中所使用的绝对地址,可在编译或汇编时给出,也可由程序员赋予。通常在程序中采用符号地址,然后在编译或汇编时,再将这些符号地址转换为绝对地址。
注意:这种方式主要由编译器负责地址转换,用于单道程序阶段(即无操作系统阶段) 。
2.3 可重定位装入方式
又叫静态重定位。
绝对装入方式只能将目标模块装入到内存中事先指定的位置。在多道程序环境下,编译程序不能预知所编译的目标模块应放在内存何处,因此绝对装入方式只适用于单道程序环境下。
在多道程序环境下,目标模块的起始地址通常从0开始,程序中的其他地址都是相对于起始地址计算的。因此应采用可重定位装入方式,根据内存的当前情况,将装入模块装入到内存的适当位置。
在装入时,对目标程序中指令和数据的修改过程称为重定位。地址变换在装入时一次完成,以后不再改变,称为静态重定位。
例如:
静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。
注意:装入程序负责地址转换,主要用于早期多道批处理阶段。
2.4 动态运行时装入方式
前瞻:重定位装入方式,可将装入模块装入到内存中任何允许的位置,故可用于多道程序环境;但并不允许程序运行时在内存中移动位置。
实际上,在运行过程中程序在内存中的位置可能经常要改变。
动态运行时的装入程序,在把装入模块装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址都仍是相对地址(逻辑地址)。为使地址转换不影响指令的执行速度,应设置一个重定位寄存器。
例如,采用动态运行时装入方式将其装入逻辑地址与内存中物理地址一样的位置,当程序开始执行指令0时,通过重定位寄存器中保存的起始位置100,将其与指令0中的逻辑地址79相加,即可得到正确的物理地址位置,程序方可正确运行下去。
采用动态可重定位时,允许程序在内存中发生移动。并且可将程序分配到不连续的存储区中,在程序运行前只需装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存;便于程序段的共享,可以向用户提供一个比存储空间大得多的地址空间。
注意:运行时才进行地址转换,主要用于现代操作系统
程序经过编译后得到一组目标模块,再利用链接程序将目标模块链接,形成装入模块。
根据链接时间的不同,把链接分成三种:
2.5 静态链接
在程序运行前,将目标模块及所需的库函数链接成一个完整的装配模块(可执行文件),以后不再拆开。
2.6 装入时动态链接
指将用户源程序编译后所得的一组目标模块,在装入内存时,采用边装入边链接的链接方式。
即在装入一个目标模块时,若发生一个外部模块调用事件,将引起装入程序去找出相应的外部目标模块,并将它装入内存,还要修改目标模块中的相对地址。
优点:
- 便于修改和更新
- 便于实现对目标模块的共享
2.7 运行时动态链接
指对某些目标模块的链接,是在程序执行中需要该目标模块时,才对它进行链接。
在许多情况下,应用程序要运行的模块可能不同,事先不知道要运行哪些模块,只能全部装入,装入时全部链接在一起,效率低。
而运行时动态链接是将对某些模块的链接推迟到执行时才执行,即在执行过程中,当发现一个被调用模块尚未装入内存时,立即由OS去找到该模块并将之装入内存,把它链接到调用者模块上。凡执行过程中未被用到的目标模块,不会调入内存和链接。
优点:这样不仅加快程序的装入过程,而且节省大量的内存空间。
这是对装入时动态链接的一种改进,当然也具备它所拥有的优点。
三、连续分配存储器管理方式
连续分配方式,是指为一个用户程序分配一个连续的内存空间。
3.1 单一连续分配
最简单的一种存储管理方式,但只能用于单用户、单任务的操作系统中。
采用这种存储管理方式时,可把内存分为系统区和用户区两部分,系统区仅提供给OS使用,通常放在内存低址部分,用户区是指除系统区以外的全部内存空间,提供给用户使用。
由于内存中只能有一道用户程序,用户程序独占整个用户区空间。导致有很大一部分用户区空闲,内存利用率低,有大量内部碎片,存储器利用率极低。
内部碎片:分配给某进程的内存区域中,如果有些部分没有用上,就是 “ 内部碎片 ”。
3.2 固定分区分配
为了能够支持多道程序的系统,内存用户空间被划分为若干个固定大小的区域,在每个分区中只装入一道作业,这样把用户空间划分为几个分区,便允许有几道作业并发执行。当有一空闲分区时,便可以再从外存的后备作业队列中,选择一个适当大小的作业装入该分区,当该作业结束时,可再从后备作业队列中找出另一作业调入该分区。
可用两种方法将内存的用户空间划分为若干个固定大小的分区:
(1) 分区大小相等:缺乏灵活性,用于一台计算机控制多个相同对象的场合
(2) 分区大小不等:把内存区划分成含有多个较小的分区、适量的中等分区及少量的大分区,可根据程序的大小为之分配适当的分区。
为便于内存分配,通常将分区按大小进行排队,并为之建立一张分区使用表,其中各表项包括每个分区的起始地址、大小及状态(是否已分配)。
有一用户程序要装入时,由内存分配程序检索该表,从中找出一个能满足要求的、尚未分配的分区,将之分配给该程序,然后将该表项中的状态置为“已分配”;若未找到大小足够的分区,则拒绝为该用户程序分配内存。
优点:实现简单,无外部碎片。
外部碎片:是指内存中的某些空闲分区由于太小而难以利用
缺点:内存利用率较低,会产生内部碎片。
内部碎片和外部碎片的区别:
内部碎片:分配出去但有些没用上
外部碎片:没分配出去但本身无法使用(太小了,难以利用)。
3.3 动态分区分配
动态分区分配不会预先划分内存分区,而是根据进程的实际需要,动态地为之分配内存空间。作业装入内存时,把可用内存分出一个连续区域给作业,且分区的大小正好适合作业大小的需要。分区的大小和个数依装入作业的需要而定。
在实现过程中,将涉及如下问题:
1、分区分配中的数据结构
2、分区分配算法
3、分区分配及回收操作
3.3.1 分区分配中的数据结构
常用的数据结构有以下两种形式:
空闲分区表:记录每个空闲分区的情况。每个空闲分区占一个表目。表目中包括:分区序号、分区始址、分区的大小等。
空闲分区链:在每个分区的起始部分,设置一些用于控制分区分配的信息,以及用于链接各分区所用的前向指针;在分区尾部则设置一后向指针,在分区末尾重复设置状态位和分区大小表目。
3.3.2 分区分配算法
为把一个新作业装入内存,需按照一定的分配算法,从空闲分区表或空闲分区链中选出一分区分配给该作业。
常用的分配算法:
(1) 首次适应算法FF
(2) 循环首次适应算法
(3) 最佳适应算法
(4) 最坏适应算法
当有很多个空闲分区都能够满足需求时,应该选择哪个分区进行分配呢? 这就取决于你选择的算法了。
【1】首次适应算法FF
FF算法要求空闲分区表以地址递增的次序排列。在分配内存时,从表首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止;然后按照作业的大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲分区表中。若从头到尾不存在满足要求的分区,则分配失败。
优点:优先利用内存低址部分的内存空间
缺点:低址部分不断划分,产生外碎片;每次查找从低址部分开始,增加了查找的开销。地址密集,高址空闲。
【2】 循环首次适应算法
在分配内存空间时,空闲分区表以地址递增的次序排列,从上次找到的空闲分区的下一个空闲分区开始查找,直到找到一个能满足要求的空闲分区,从中划出一块与请求大小相等的内存空间分配给作业。
为实现算法,需要:
1、设置一个起始查寻指针
2、采用循环查找方式(当最后一个空闲分区也不能满足要求,则应回到第一个空闲分区)
优点:使内存空闲分区分布均匀,减少查找的开销
缺点:缺乏大的空闲分区
【3】 最佳适应算法
所谓“最佳”是指每次为作业分配内存时,总是把能满足要求、又是最小的空闲分区分配给作业,避免“大材小用”。
要求将所有的空闲分区按其容量以从小到大的顺序形成一空闲分区链。
缺点:产生许多难以利用的小空闲区,即外部碎片。
【4】最坏适应算法
算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
如何实现:空闲分区按其容量从大到小链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。
3.3.3 分区分配及回收操作
【1】分配内存
利用某种分配算法,从空闲分区链(表)中找到所需大小的分区。设请求的分区大小为u.size,表中每个空闲分区的大小表示为m.size,若m.size- u.size <= size(规定的不再切割的分区大小),将整个分区分配给请求者,否则从分区中按请求的大小划出一块内存空间分配出去,余下部分留在空闲链中,将分配区首址返回给调用者。
【2】回收内存
当进程运行完毕释放内存时,系统根据回收区首址(释放区首址),在空闲分区链(表)中找到相应插入点,此时可能有四种情况:
(1) 如上图A,释放区与插入点的前一个空闲分区F1邻接:将该回收区与F1合并,修改F1的表项的分区大小。
(2) 如上图B,回收区与插入点的后一个空闲分区F2邻接:将回收区与F2合并,修改F2的表项的首址、分区大小。
(3) 如上图C,回收区与插入点的前后两个空闲分区F1、F2邻接:将三个分区合并,使用F1的表项和F1的首址,取消F2的表项,大小为三者之和。
(4) 如上图D,回收区不和任何空闲区邻接,那么就要为该回收区单独建立新表项,填写回收区的首址与大小,根据其首址插到空闲链中的适当位置。
3.4 可重定位分区分配
在连续分配方式中,必须把系统或用户程序装入一连续的内存空间。如果在系统中只有若干个小分区,即使它们的容量总和大于要装入的程序,但由于这些分区不相邻,所以无法将程序装入内存。
解决方法:将内存中的所有作业进行移动,使它们全部邻接,这样可把原来分散的小分区拼接成大分区,这种方法称为“拼接”或“紧凑”。
缺点:用户程序在内存中的地址发生变化,必须重定位。
动态重定位分区分配算法与动态分区分配算法基本相同,差别在于增加了紧凑的功能。
前面讲的动态分区分配:无内部碎片,有外部碎片。
但是利用可重定位分区分配的紧凑功能,可以解决外部碎片。
四、对换
4.1 对换的引入
多道程序环境下存在的问题:
1、阻塞进程占据大量内存空间
2、许多作业在外存而不能进入内存运行
对换:把内存中暂时不能运行的进程或者暂时不用的程序和数据,调到外存上,以便腾出足够的内存空间,再把已具备运行条件的进程和进程所需要的程序和数据,调入内存。
而对换的这种功能可以很好的解决这些问题!
对换的分类:
- 整体对换(或进程对换):以整个进程为单位(这也没啥可介绍的)
- 页面对换或分段对换:以页或段为单位(在下一章虚拟存储器中介绍)
实现进程对换,系统必须具备的功能:
1、对换空间的管理
2、进程的换出
3、进程的换入
4.2 对换空间的管理
一般从磁盘上划出一块空间作为对换区使用,其余空间用作文件区 :
在系统中设置相应的数据结构以记录外存的使用情况,并且对换空间的分配与回收,与动态分区方式时的内存分配与回收雷同,所以就不阐述了。
4.3 进程的换出与换入
进程的换出:
换出过程:系统首先选择处于阻塞状态且优先级最低的进程作为换出进程,然后启动盘块,将该进程的程序和数据传送到磁盘的对换区上。
进程的换入:
换入过程:系统应定时查看所有进程的状态,从中找出“就绪”状态但已换出的进程,将换出进程最久的进程作为换入进程,将之换入,直至已无可换入的进程或无可换出的进程为止。
五、分页存储管理方式
连续分配方式会形成“碎片”,虽然可以通过“紧凑”解决,但开销大。如果允许将一个进程直接分散地装入许多不相邻的分区中,则无需“紧凑”,由此产生离散分配方式。
将内存空间分为一个个大小相等的分区(比如:每个分区4KB),每个分区就是一个“页框”(页框=页帧=内存块=物理块=物理页面)。每个页框有一个编号,即“页框号”(页框号=页帧号=内存块号=物理块号=物理页号),页框号从0开始。
将进程的逻辑地址空间也分为与页框大小相等的一个个部分,每个部分称为一个“页”或“页面”。每个页面也有一个编号,即“页号”,页号也是从0开始。
操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。各个页面不必连续存放,可以放到不相邻的各个页框中。
当然进程不可能每次都能恰好等分成与页框大小相等的一个个部分,即进程的最后一页可能与页框大小不等,因此会因为装不满而形成“页内碎片”。但做题时,不用考虑这些,一般都是可以等分的。
5.1 页表
为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表。
注:页表通常存在PCB (进程控制块)中
1.一个进程对应一张页表
2.进程的每个页面对应一个页表项
3.每个页表项由“页号”和“块号”组成
4.页表记录进程页面和实际存放的内存块之间的映射关系
考点1:
- 计算内存块的数量
- 页表项中块号至少占多少字节
e.g: 假设某系统物理内存大小为4GB, 页面大小为4KB, 计算内存块的数量?计算每个页表项至少应该为多少字节?(注意是字节)
解:
→ 内存块大小=页面大小=4KB=2¹²B
→ 4GB的内存总共会被分为2³²/2¹²=2²⁰个内存块
→ 所以内存块号的范围应该是0∼2²⁰-1
→ 那么内存块号至少要用20 bit来表示
→ 因此至少要用3B来表示块号( 3×8=24bit)
→ 由于页号是隐含,并不占存储空间,因此每个页表项占3B,存储整个页表至少需要3*(n+1)B
之所以页号不占存储空间,是因为页表各项本身就是连续存放的(页表这种结构就类似一维数组,页号是下标,块号是数组值) 。
就拿上面那个例题而言:假设页表中的各页表项从逻辑地址为 X 的地方开始连续存放...
如何找到页号为i的页表项?
那么 i 号页表项的存放地址为:X + 3 * i
5.2 分页存储管理中的逻辑地址结构
考点2:
- 如何确定一个逻辑地址对应的页号、页内偏移量
这是有公式的:
- 页号 = 逻辑地址/页面长度 (取除法的整数部分)
- 页内偏移量 = 逻辑地址%页面长度(取除法的余数部分)
(1)通过一个简单的图形介绍一下:
页号 = 110 / 50= 2
页内偏移量 = 110 % 50 = 10
(2)从二进制的角度理解:
5.3 由逻辑地址求物理地址
通过下面两个经典题型,让你瞬间秒懂!
- 逻辑地址 = 页号 + 页内地址
- (适用页面大小刚好是2的整数次幂)物理地址 = 内存块号 + 页内地址
- (超级万能)物理地址 = 页面在内存中存放的起始地址 + 页内地址
题型一:通过十进制计算(用超级万能)。
已知某个分页系统,页面大小为1K(即1024字节),某一个作业有4个页面,分别装入到主存的第3、4、6、8块中,求逻辑地址2100对应的物理地址。
解:
- 求该逻辑地址的页号 = 2100 / 1024=2 (整除)
- 求它的页内偏移量 = 2100 % 1024 =52 (取余)
- 根据题目产生页表:
页号 页框号/帧号
0 3
1 4
2 6
3 8- 如上图,逻辑地址的第2页对应物理地址的第6块。
- 页面在内存中存放的起始地址 = 内存块号 * 页面大小
- 求出物理地址 = 6*1024 + 52 = 6196
题型二:(一般会给你8进制或16进制,建议先转成2进制计算)通过二进制计算。
(因为页面大小刚好是2的整数次幂,并且是采用2进制计算,所以用这个公式:物理地址 = 内存块号 + 页内地址)
已知分页存储管理系统中逻辑地址长度为16位,页面大小为4KB字节,现有一逻辑地址为3C20H,且第0、1、2、3页依次存放在物理块2、3、5、6中。求逻辑地址3C20H对应的物理地址
解:
- 将逻辑地址3C20H转换为二进制为:0011 1100 0010 0000
- 由于页面大小为4KB字节,(4KB=2^12)。所以逻辑地址的后12位为“页内地址”,那么剩下的前4位为页号:即0011为页号
- 根据页表可知,0011(十进制为3)对应的页框号(内存块号)为6(二进制为0110)
- 所以最终的物理地址为:0110 1100 0010 0000
即 6C20H
通过题型就能说明一切,对于题型二,如果页面大小不是2的整数次幂,那么就很难将逻辑地址拆分成:页号 + 页内地址。但是如果页面大小不是2的整数次幂,我们可以用超级万能解决,只是对于这种二进制的计算比较麻烦一点,需要先将其转成题型一那样。
大部分题型的页面大小都是2的整数次幂,除非你们老师喜欢刁钻的出题,hh。
5.4 地址变换机构
上面讲的是手算实现逻辑地址向物理地址的转换,如果你理解了上述思想,那么下面讲的地址变换机构,就是从计算机的角度实现这个变换。
由于寄存器成本高,系统页表可能很大,所以页表大多常驻内存。
在系统中只设置一个页表寄存器PTR,在其中存放页表在内存中的始址和页表的长度(即多少页)。
实现步骤如下:
详细介绍:(前提:页面大小是2的整数幂)
通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址F和页表长度M。进程未执行时,页表的始址和页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。
设页面大小为L,逻辑地址A 到 物理地址E的变换过程如下;
① 计算页号P 和 页内偏移量W
② 比较页号P 和页表长度M,若 P ≥ M,则产生越界中断,否则继续执行。
- 注意:页号是从0开始的,而页表长度至少是1,因此P=N时也会越界
③ 页表中页号P对应的页表项地址=页表起始地址F+页号P*页表项长度,取出该页表项内容b,即为内存块号。
- 注意区分页表项长度、页表长度、页面大小的区别。
- 页表长度指的是这个页表中总共有几个页表项,即总共有几个页;
- 页表项长度指的是每个页表项占多大的存储空间;
- 页面大小指的是一个页面占多大的存储空间
④ 计算E = b * L + W,用得到的物理地址E去访存。
- 如果内存块号、页面偏移量是用二进制表示的,那么把二者拼接起来就是最终的物理地址了
看到这里,是不是和我们手算几乎一模一样啊,只是计算机算的更快!
5.5 具有快表的地址变换机构
上述介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。由于局部性原理,可能连续很多次查到的都是同一个页表项 ,我们难道不能把第一次查到的信息存下来嘛?因此块表就诞生了!
快表, 又称联想寄存器(TLB, translation lookaside buffer) , 是一种访问速度比内存快很多的高速缓存(TLB不是内存!),用来存放最近访问的页表项的副本,可以加速地址变换的速度。与此对应,内存中的页表常称为慢表。
有点Cache的味道了~
若第一个(0,0)的逻辑地址被访存后,会向 快表 里记录页号为0对应的内存块号,接下来,如果还有同一页号的逻辑地址,就不需要再去访问内存中的页表,查询内存块号了。
详细步骤如下:
① CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
② 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。(即访存目标页面)
③ 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存。(即访存页表 + 访存目标页面)
- 注意:在找到页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换
由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间。
例:某系统使用基本分页存储管理,并采用了具有快表的地址变换机构。访问一次快表耗时1us,访问一次内存耗时100us。若快表的命中率为90%,那么访问一个逻辑地址的平均耗时是多少?
(1+100)*0.9+(1+100+100)⋅0.1=111us
有的系统支持快表和慢表同时查找,如果是这样,平均耗时应该是((1+100)*0.9+(100+100)*0.1=110.9 us
若未采用快表机制,则访问一个逻辑地址需要100+100=200us
总结 :
5.6 两级和多级页表
例子:一个具有32位逻辑地址空间的分页系统,规定页面大小为4KB即B,则每个进程页表的页表项可达1M个,若每个页表项占用一个字节,则每个进程的页表就要占据1MB的内存空间,而且要求连续存放。
内存想要做到上述条件是比较吃力的!!!
解决方法:
- 采用离散方式
- 只将当前所需页表项调入内存
这里只介绍第一种方法,对于第二种方法在下一章介绍(主要运用虚拟存储技术)。
当然对于第一种方式,我们也只介绍两级页表,无限套娃没有意思~
两级页表定义:
将页表分页,并离散地将各个页面分别存放在不同的物理块中,同时为离散分配的页表在建立一张页表,称为外层页表(或页目录表),其每个页表项记录了页表页面的物理块号。
其逻辑地址结构,其实就是将其页号在进一步划分为一级页号和二级页号。
例如: 32位逻辑地址空间,页面大小为4KB(即12位),若采用一级页表机构,应有20位页号,即页表项应有1M个;在采用两级页表机构时,再对页表进行分页,使每页包含(即1024)个页表项,最多允许有个页表分页。即:
例题:就拿上述那个图为例,将逻辑地址(0000000000 0000000001 111111111111)转换为物理地址(用十进制表示)。
解:
一级页号为:0(从内存块号1011读出第0页页表),二级页号为:1,那么就能快速定位到4号内存块,然后用超级万能解题:
→ 4号内存块的起始地址为:4 * 4096 = 16384
→ 页内偏移量为4095
→ 最终的物理地址为:16384 + 4095
注意:两级页表的访存次数(假设没有快表)为:3次
- 第一次:访问内存中的外层页表
- 第二次:访问内存中的二级页表
- 第三次:访问目标内存单元
六、分段存储管理方式
分页存储管理的缺点:无论信息内容如何,按页长分割,分割后装入内存,有可能主程序未能全部进入内存,-----执行速度降低。
因此基于以上问题,分段存储管理方式考虑以逻辑单位分配内存。如:
进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。
内存分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。
6.1 分段的逻辑结构
每个段定义了一组逻辑信息,都有自己的名字。通常用段号代替段名。
逻辑地址由段号(段名)和段内地址所组成:
段号决定了每个进程最多可以分几个段。
段内地址决定了每个进程的最大长度。
假设上述按字节寻址。那么该地址结构允许一个作业最长有64K个段,每段的最大长度为64KB。
6.2 段表
在分段式存储管理系统中,系统为每个分段分配一个连续的分区,而进程中的各个段可以离散地移入内存中不同的分区中。
为使程序正常运行,须在系统中为每个进程建立一张段映射表,简称“段表”。每个段在表中占有一个表项。
段表结构:段号;段在内存中的起始地址(基址);段长。
6.3 地址变换机构
在系统中设置段表寄存器,用于存放段表始址和段表长度,以实现从进程的逻辑地址到物理地址的变换。
有了上面分页的基础,这个也就没什么可讲的了,唯一要注意的是,有两项检测:
- 判断段号是否越界
- 判断段内地址是否超过段长
当然还有具有 快表 的地址变换机构,这里就不做介绍了,和分页雷同!
6.4 分段、分页的对比
相似点:
- 采用离散分配方式,通过地址映射机构实现地址变换
不同点:(已经尽量帮大家精炼了,这样好记好写)
- 页是信息的物理单位,分页对用户不可见;段是信息的逻辑单位,分段对用户可见
- 页的大小固定且由系统确定;段的长度不固定,取决于用户程序
- 分页的作业地址空间是一维的;分段的作业地址空间是二维的
- 分段比分页更容易实现信息的共享和保护
对于上述不同点,有些可能对同学来说很难理解,因此有必要解释一下:
【1】对于不同点1,分页仅仅是系统管理上的需要,完全是系统行为,用户不可见;而分段主要满足用户需求,用户编程时需要显式地给出段名(比如某个子函数名)
【2】对于不同点3,通过分页来确定物理地址,我们只需要知道逻辑地址就可以,因为页面大小是固定的(通过整除和取余可以得到页号和页内偏移量),所以分页是一维的;而通过分段确定地址时,只给出逻辑地址没有办法通过整除和取余可以得到页号和页内偏移量(因为段大小不同),需要程序员显示给出段名和段内地址。
【3】对于不同点4,
- 分段比分页更容易共享:分页实现共享肯定很困难,因为你不能保证一个页里面只有非临界资源,因为其大小固定,所以不像段那么灵活。
- 分段比分页更容易实现信息的保护:和上面的理由一样,分页大小固定,可能这个页面既包含一点临界资源,也包含一点非临界资源,这是不能允许的。
- 补充:可重入代码又称为纯代码,是一种允许多个进程同时访问的代码(共享),可重入代码不允许任何进程对它进行修改(记着就行,应付填空题)。
七、段页式管理方式
分段和分页存储管理方式各有优缺点。
把两者结合成一种新的存储管理方式——段页式存储管理方式,具有两者的长处。
基本原理:先将用户程序分成若干段,并为每个段赋予一个段名,再把每个段分成若干页。
段号的位数决定了每个进程最多可以分几个段
页号位数决定了每个段最多有多少页
页内偏移量决定了页面大小\内存块大小是多少在上述例子中,若系统是按字节寻址的,则
段号占16位,因此在该系统中,每个进程最多有2¹⁶=64K个段
页号占4位,因此每个段最多有2⁴=16页
页内偏移量占12位,因此每个页面\每个内存块大小为2¹²=4096=4KB
“分段”对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。
因此段页式管理的地址结构是二维的。
对应关系如下
一句话总结:一个进程分为多个段 ,一个段对应一个页表, 一个进程就对应多个页表。
再来介绍下它的段表和页表:就拿上述0号段为例子
每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。
每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。
地址变换机构如下:
看图就能很轻松理解。
在段页式系统中,为了获得一条指令或数据,需访问三次内存:
第一次:访问内存中的段表,取得页表始址(页表存放块号)
第二次:访问内存中的页表,取得该页所在的物理块号,将块号与页内地址形成物理地址
第三次:访问第二次所得的地址,取出指令或数据
当然也可引入快表(如果命中,则只需要一次访存)。
格外的课后知识:
现代操作系统主要采用分页存储,段(页)式存储已经基本见不到,因为段式存储的优点几乎没有体现了,现在没有人在编程的时候还考虑程序分段。并且段页式也会增加系统的复杂度。当然也有段的思想在里面,但是现代操作系统通过巧妙的设置,已经“ 屏蔽 ”了段的存在。
当然这也不在本章的探讨范围内,并且博主实力也有限,不能做更多详细解释,就当做一个课外知识吧!
八、结尾
最后,非常感谢大家的阅读。我接下来还会更新第五章:【虚拟存储器】 ,如果本文有错误或者不足的地方请在评论区(或者私信)留言,一定尽量满足大家,如果对大家有帮助,还望三连一下啦!
Reference
【1】 汤子瀛, 哲凤屏, 汤小丹,梁红兵. 计算机操作系统[第四版]. 西安电子科技大学出版社