内存管理中的核心问题

  • 存储分配:主要是讨论和解决躲到作业之间共享主存的存储空间的问题(虚拟地址空间),怎么为作业分配物理空间?
  • 存储器资源的组织:区、段、页式。
  • 地址转换:页表、段表逻辑地址与物理地址的转换。
  • 虚拟存储调度问题:页面置换算法。

分区式存储管理

单道程序的内存分配 – 静态分配

对于单任务操作系统(内存中只有两个程序:一个用户程序、一个操作系统),由于操作系统所占空间是固定的,因此用户程序永远加载到同一个地址,并且可以在预先编译加载的时候计算出用户程序的地址。所以对于单道程序的内存管理就直接简单粗暴的加载上去就行,没有什么技巧。不再赘述。

多道程序的内存分配 – 动态分配

多道程序的内存分配采用分区式分配的方法。把内存分为一些大小相等或不等的分区,每个应用程序占用一个或几个分区,操作系统占用其中一个分区。具体实现中又分为:固定式分区和可变式分区。

  • 固定式分区:分区大小可以不相同,但是一定是固定的,这就意味着需要程序去适配分区,即可能会出现内碎片(一个分区内有空白空间未使用)。具体分配方式上又有单一队列分配和多队列分配。
    • 单一队列:所有程序都在同一个队列中等待分配,每次加载的时候,选择一个当前最大的分区进行加载。
    • 多队列:由于程序大小和分区大小不一定匹配,有可能形成一个小程序占用一个大分区的情况,从而造成内存里虽然有小分区闲置但无法加载大程序的情况。这时,可以采用多个队列,给每个分区一个队列,程序按照大小排在相应的队列里。
  • 可变式分区:分区边界可以移动,即分区大小可变。优点是没有内碎片,但是会出现外碎片。(这些外碎片,可以通过紧凑技术移动已分配的分区的地址空间,将零碎的空闲分区合成一个大的空闲分区,不过这个移动的开销较大)

空闲空间的管理

对于内存空间中空闲空间的管理方式,主要有两种:位图法、分区链表法。

  • 位图法:给每个分配单元赋予一个字位,用来记录该分配单元是否闲置。例如,字位取值为0表示单元闲置,取值为1则表示已被占用。
    • 空间成本固定、时间成本低、没有容错能力(除非设什么校验码的)
  • 分区链表法:将内存空间单元以链表的形式顺序链接起来,每个结点需包含是否空闲、单元大小、单元的起始地址、下一个结点的指针等信息。
    • 空间成本取决于程序数量、时间成本高、有一定容错能力(因为是占用空间和空闲空间相间分布的,所以一定程度上可以互相验证)。

可变分区分配算法

由于一个可变分区分配之后,内部未使用的空闲分区可以再划分出来成为新的空闲分区,所以可变分区的分配方法比固定分区复杂一点。总的来说,可变分区的分配方法有两大类:基于链表的顺序搜索的分配算法、基于索引的分配算法。首先,介绍基于链表的顺序搜索的分配算法。

  • 首次适应算法(First Fit):每个空白区按其在存储空间中地址递增的顺序连在一起,在为作业分配存储区域时,从这个空白区域链的始端开始查找,选择第一个足以满足请求的空白块。
    • 优先利用内存低地址部分的空闲分区。但由于低地址部分不断被划分,留下许多难以利用的很小的空闲分区(碎片或零头),而每次查找又都是从低地址部分开始,增加了查找可用空闲分区的开销。
  • 下次适应算法(Next Fit):把存储空间中空白区构成一个循环链,每次为存储请求查找合适的分区时,总是从上次查找结束的地方开始,只要找到一个足够大的空白区,就将它划分后分配出去。
    • 使存储空间的利用更加均衡,不致使小的空闲区集中在存储区的一端,但这会导致缺乏大的空闲分区。
  • 最佳适应算法(Best Fit):为一个作业选择分区时,总是寻找其大小最接近于作业所要求的存储区域。
    • 因为若未匹配到合适大小的分区,则会分配稍微大一点点的分区,这样会造成许多很小的外碎片而无法被利用。
  • 最坏适应算法(Worst Fit):为作业选择存储区域时,总是寻找最大的空白区。
    • 因为总是划分最大的空闲区,容易使得后来的大作业无法装下。

由于当系统分区很多时,再采用链表进行顺序搜索速度就会较慢,所以在大中型系统中采用了基于索引搜索的动态分配算法。

  • 快速适应算法:又称为分类搜索法,把空闲分区按容量大小进行分类,经常用到长度的空闲区设立单独的空闲区链表。系统为多个空闲链表设立一张管理索引表。
    • 优点:查找效率高,仅需要根据程序的长度,寻找到能容纳它的最小空闲区链表,取下第一块进行分配即可。 该算法在分配时,不会对任何分区产生分割,所以能保留大的分区,也不会产生内存碎片。(有点像固定式分区的分配方法了)。
    • 缺点:在分区归还主存时算法复杂,系统开销较大。在分配空闲分区时是以进程为单位,一个分区只属于一个进程,存在一定的浪费。空间换时间。
  • 伙伴系统:伙伴系统 (buddy system)是介于固定分区与可变分区之间的动态分区技术。在分配存储块时将一个大的存储块分裂成两个大小相等的小块,这两个小块就称为“伙伴”。
    • 规定:所有分区大小均为 2 的 k 次幂,内存管理中按空闲块的大小分类维护多个空闲块链表(这个同快速适应算法,但采用了伙伴的概念优化了回收工作),系统启动初始只有一个最大的空闲块(整个内存)。
    • 分配原则:对于一个长度为 n 的进程申请内存,系统会求出 2i1<n2i2^{i-1} < n \leq 2^i 中的 2i2^i 是多少,并去查询这个大小的空闲链表,若链表不为空,则取出第一个结点分配之;若链表为空,则向上找 2i+12^{i+1} 的链表是否为空,不为空就取出第一个结点,切一半分配之,若仍为空则按类似原则向上找。
    • 回收合并原则:只有两个存储块是伙伴,才会合并在一起。

页式内存管理

由于分区式管理,每道程序都占用一个或几个连续空间(分区),当内存无足够大的连续空间时程序无法装入。当内存短缺的时候,虽然可以采用移动内存中的某些程序的方法(紧凑方法)来尝试获得更大的空闲连续空间,但是这样系统开销又很大。

人们便希望,如果可以把一个逻辑地址连续的的程序分散存放到若干不连续的内存区域内,并保证程序的正确执行,则既可充分利用内存空间,又可减少移动带来的开销。这便是页式管理的基本思想。而且采用页式管理,便很容易实现页共享,不同的虚拟页可以被映射到同一个物理页框中。

为了实现页式管理,人们便引入了虚拟地址空间和与之配对虚拟地址与物理地址的转换机制。一般来说,具体实现中,每个进程会维护一个自己的页表来标识逻辑页号与物理页框号之间的映射关系;操作系统会维护一个当前内存中空闲页的链表;每个进程 PCB 里面会记录该进程页表被存放在哪一个物理页框中。这样每个进程中虚拟地址的转换只需要查找相应的页表即可,但是由于每次内存访问的时候需要查询页表,而这么做会带来额外的开销,特别是当页表项非常多的时候,即使采用多级页表也不可忍受。为此,人们提出了页表快速访问机制(MMU)等机制。

页表快速访问机制(MMU)

为了提高地址转换效率,CPU 内部增加了一个硬件单元,存储管理单元 MMU。其内部主要部件:

  • 页表 Cache:又称 TLB 快表,用于存放虚拟地址与相应的物理地址。因为 Cache 中存放的条目较少,所以查起来很快。除此之外,快表中每一项还会又一个地址空间标识码 ASID,表明这个表项是在哪一个进程地址空间下的,这样切换进程的时候,就不用重刷一次 TLB 了。
  • TLB 控制单元:TLB 内容的填充、刷新、覆盖,以及越界检查。
  • 页表遍历查找单元:若 TLB 未命中,自动查找多级页表,将找到的物理地址送至 TLB 控制单元进行填充或者覆盖。

哈希表页

将页表换成一个哈希散列表,即键为逻辑页号的哈希值,值为其对应的物理页框号(若有冲突简单的用链表法解决即可)。这样感觉可能又是一种以空间换时间的方法吧。

反置页表

一般意义上,每个进程都有一个相关页表。该进程所使用的每个页都在页表中有一项。这种页的表示方式比较自然,这是因为进程是通过页的虚拟地址来引用页的。操作系统必须将这种引用转换成物理内存地址。

这种方法的缺点之一是每个页表可能有很多项。这些表可能消耗大量物理内存,却仅用来跟踪物理内存是如何使用的。如每个使用 32 位逻辑地址的进程其页表长度均为 4MB(二级页表)。

为了解决这个问题,可以使用反置页表。反置页表不是依据进程的逻辑页号来组织,而是依据该进程在内存中的物理页面号来组织(即按物理页面号排列),其表项的内容是逻辑页号 P 及隶属进程标志符 pid。

在地址转换的时候,需要依靠进程标识符和页号去遍历反置页表(因为物理空间往往比逻辑空间小,所反置页表会比一般的页表小很多),若检索到与之匹配的页表项,则该表项的序号 i 便是该页的物理块号;若未找到,则触发 pagefault 缺页中断,让系统内核判断是重新加载此页还是地址出错。

段式存储管理

在分段地址空间中,一段可以定义为一组逻辑信息(数据段、程序段),每个作业地址空间是由一些分段构成的,每个段都有自己的名字(通常是段号),且段内的地址是连续的但是段与段之间可以不是。所以与分区不同,段是局部连续的。

段式与页式的区别

个人认为,段式较页式更好的点在于信息共享。因为信息共享是以信息的逻辑单位为基础的,而段正是信息的最小逻辑单位。所以,在页式管理中可能会出现的一个页面装有两个不同的子程序的指令代码,一个子程序需要共享一个不能,而这是不能够通过页共享来实现的。但是这种尴尬场景不会出现在段式存储之中。

缺点是:处理机要为地址变换花费更多的时间;为了满足分段的动态增长和减少外碎片,可能需要采用拼接紧凑的技术;在辅存中管理不定长度的分段困难较多;分段的最大尺寸受到主存可用空间的限制。

地址空间上,段式的逻辑地址并不是简单的 32 位,前多少位是段号,跟页不同,段式存储下的访存都是指定(段号,段内偏移)一个二元组完成的,所以页式地址空间是一个单一的一维空间,段式却是二维的。

OS 学习记录 - 实存管理

段的地址转换机制

段的地址转换机制大体上与页一样,逻辑地址被分为段号与段内偏移两部分(页式中式页号和页内偏移),然后通过查找段表找到段对应的物理起始地址即可。不同点在于段表项除了保存段号与段物理基址的映射关系之外,还需要记录这个段有多大,来进行访问时候的越界检查。

段页式存储管理

基本思想:用分段的方法来分配和管理虚拟存储器,而用分页的方法来分配和管理实存储器。具体实现上,地址结构由段号、段内页号、及页内偏移构成。系统中设有段表、页表。每个进程一个段表,每个段一个页表。段表项变为段号、页表起始地址、页表长度,页表项内容不变。

OS 学习记录 - 实存管理

段页式存储管理的地址变换

  1. 从 PCB 中取出段表始址和段表长度,装入段表寄存器。

  2. 将段号与段表长度进行比较,若段号大于或等于段表长度,产生越界中断。

  3. 利用段表始址与段号得到该段表项在段表中的位置。取出该段的页表始址和页表长度。

  4. 将页号与页表长度进行比较,若页号大于或等于页表长度,产生越界中断。

  5. 利用页表始址与页号得到该页表项在页表中的位置。

  6. 取出该页的物理块号,与页内地址拼接得到实际的物理地址。

相关文章:

  • 2021-06-05
  • 2021-08-20
  • 2022-01-13
  • 2022-01-15
  • 2021-08-19
  • 2021-10-12
  • 2021-09-19
猜你喜欢
  • 2021-09-05
  • 2021-10-30
  • 2021-11-21
  • 2022-03-08
  • 2021-09-06
  • 2021-08-11
  • 2021-07-03
相关资源
相似解决方案