0x15 虚拟内存(下)

四、页框分配和颠簸

页框分配

也称帧分配,就是研究如何为各个进程分配一定的空闲内存。
如:现有93个空闲页框和2个进程,那么每个进程各得到多少页框?

页框分配会受到多方面的限制,例如,所分配的页框不能超过可用页框的数量,也必须分配至少最少的页框数,即每个进程所需要的最少的页框数。

分配至少最少的页框数的原因之一是性能。当指令完成之前出现缺页中断时,该指令必须重新执行,因此,必须有足够的页框来容纳所有单个指令所引用的页。

必须满足:每个进程所需要最少的页数
例子:IBM370-6处理SS MOVE指令:

  • 指令是6个字节,可能跨越2页
  • 2页处理from
  • 2页处理to

两个主要的分配策略:

  • 固定分配
  • 优先级分配

固定分配

为每个进程分配固定数量的页框数,有两种分配方式:平均分配和按比率分配。

平均分配——均分法
例:如果有100个页框,和5个进程,则每个进程分给20个页
按比率分配——根据每个进程的大小来分配
在这里插入图片描述

优先级分配

根据优先级而不是进程大小来使用比率分配策略。

如果进程Pi产生一个缺页

  • 选择替换其中的一个页框
  • 从一个较低优先级的进程中选择一个页面来替换

全局置换和局部置换

影响页框分配的另一个因素是页框置换。当有多个进程竞争页框时,页面置换算法可分为两大类:全局置换和局部置换。

  • 全局置换——进程在所有的页框中选择一个替换页面;一个进程可以从另一个进程中获得页框。一个问题是进程不能控制缺页率。
  • 局部置换——每个进程只从属于它自己的页框中选择。不能使用其他进程的不常用内存,所以会阻碍一个进程。

全局置换通常有更好的系统吞吐量且更为常用。

颠簸

当一个进程没有足够的页框,那么它很快产生缺页。然而,其所有页都在使用,它置换了一个页,但又立刻再次需要这个页。因此,会一而再地产生缺页中断,换出一个页,该页又立即需要换入。这种情况称为颠簸。

如果一个进程没有足够的页,那么缺页率将很高,这将导致:

  • CPU利用率低下.
  • 操作系统认为需要增加多道程序的道数
  • 系统中将加入一个新的进程

颠簸(抖动)=一个进程的页面经常换入换出
在这里插入图片描述
该图显示了CPU利用率与多道程序设计的道数之间的关系。初始时,道数增加,CPU利用率增加,直到达到最大值,如果此时道数还要继续增加,则开始系统颠簸,CPU利用率急剧下降,此时,为了增加CPU利用率和降低系统颠簸,必须降低多道程序的道数。

引发这种现象的原因是:系统内存不足页面置换算法不合理

局部模型

局部置换算法可以限制系统颠簸,如果一个进程开始颠簸,那么他不能置换其他进程的页面。为了防止颠簸,必须提供给进程足够的页框,但是如何知道进程需要多少页框呢?
常用的方法有工作集策略。这种方法定义了局部模型

当进程执行时,进程从一个局部移到另一个局部,局部可能重叠。如果为每个进程都分配了满足当前局部的页框,那么一旦该进程在其局部内出现的缺页都调入内存后,进程就不再出现缺页,直到改变局部为止。

工作集模型

△ = 工作集窗口 = 固定数目的页的引用
例如:1000个引用,△ = 1000

WSSi(进程Pi的工作集)= 最近△ 中所有页的引用(随时间变化)

工作集大小

  • 如果△太小,那么它不能包含整个局部
  • 如果△太大,那么它可能包含多个局部
  • 如果△ = ∞,那么工作集合为进程执行所接触到的所有页的集合

D = ∑WSSi = 总的页框需求量

如果D > m(可用页框数量),有的进程就会得不到足够的页框,从而出现颠簸。

策略:如果还有空闲框,可启动另一个进程;如果D>m,操作系统会暂停一个进程,该进程的页被写出,且其页框可分配给其他进程,挂起的进程可在以后重启。

工作集策略防止了颠簸,同时尽可能地提高了多道程序的道数,因此它提高了CPU的利用率。

工作集策略的困难在于跟踪工作集,工作集窗口是一个活动窗口,在每次引用时会增加新引用,最老的引用会失去,可以通过固定时钟中断和引用位来近似的模拟工作集模型。

缺页率(PFF)策略

工作集策略可用于预先调页,但是用于控制颠簸有点不太灵活,一种更直接的方法是采用缺页率策略。

颠簸具有很高的缺页率,因此需要控制缺页率,缺页率太高,进程需要更多的页框,缺页率太低,进程可能有太多的页框。

在这里插入图片描述
所以,如图显示的,我们可以设置可接受的缺页率上限和下限,如果缺页率太高(高于上限),就分给进程一些页框;如果缺页率太低(低于下限),回收一些进程的页框,与工作集策略一样,也可能必须暂停一个进程;如果缺页率增加,且没有可用的页框,必须选择一个进程暂停,接着将释放的页框分配给那些具有高缺页率的进程。

五、内核内存分配

内核内存分配

用户态进程需要内存时,从空闲页框链表中获得空闲页,这些页通常是分散在物理内存中。
但是内核内存分配不同于用户内存分配。通常从空闲内存池中获取,原因如下:
(1)内核需要为不同大小的数据结构分配内存;
(2)一些内核内存需要连续的物理页。

内核使用内存块的特点

内核在使用内存块时有以下特点:
(1)内存块的尺寸比较小;
(2)占用内存块的时间比较短;
(3)要求快速完成分配和回收;
(4)不参与交换
(5)频繁使用尺寸相同的内存块,存放同一结构的数据;
(6)要求动态分配和回收。

两个内核进程进行内存管理的方法:伙伴系统和slab分配

伙伴(Buddy)系统

主要用于Linux早期版本中内核底层内存管理;
是一种经典的内存分配方案;
从物理上连续的大小固定的段上分配内存;
主要思想:内存按2的幂的大小进行划分,即4KB、8KB等,组成若干空闲块链表;查找链表找到满足进程需求的最佳匹配块

  • 满足要求是以2的幂为单位的;
  • 如果请求不为2的幂,则需要调整到下-一个更大的2的幂;
  • 当分配需求小于现在可用内存时,当前段就分为两个更小的2的幂段,继续上述操作直到合适的段大小;

算法

首先将整个可用空间看作一块:2n,假设进程申请的空间大小为s,如果满足2n-1<s<=2n,则分配整个块,否则,将块划分为两个大小相等的伙伴,大小为2n-1,一直划分下去直到产生大于或等于s的最小块
在这里插入图片描述
如上图的例子,假定一内存段的大小为256KB,内核申请21KB的内存,那么内存段首先分为两个128KB的段,然后再分为两个64KB,再分为两个32KB,32KB之后再分就比21KB小,所以这个CL用来满足21KB的请求。

优点:可通过合并而快速形成更大的段。
缺点:调整到下一个2的幂容易产生碎片。
可能有50%的内存会因碎片而浪费。
32KB-21KB=11KB

slab分配

内核分配的另一方案;
Slab是由一个或多个物理上连续的页组成;
Cache 含有一个或多个slab;
每个内核数据结构都有一个cache,如:进程描述符、文件对象、信号量等,每个cache含有内核数据结构的对象实例。
信号量cache存储着信号量对象,进程描述符cache存储着进程猫述符对象。

下面这张图描述了slab,cache和内核对象之间的关系。
有2个3KB的内核对象和3个7KB的内核对象,它们分别位于各自的cache上。一个cache含有4个slab,另一个含有3个slab。
在这里插入图片描述
slab分配算法采用cache存储内核对象。当创建 cache时,包括若干个标记为空闲的对象,对象的数量与slab的大小有关;当需要内核对象时,从cache上直接获取,并标识对象为使用
例如,在Linux系统中,进程描述符PCB的类型为struct task_struct,其大小约为1.7KB。当Linux内核创建新任务是,它会从cache中获得task_struct对象所需要的内存。Cache上会有已分配好的并标记为空闲的task_struct对象来满足。

Slab有三种状态
满的:slab中所有对象被标记为使用
空的:slab中所有对象被标记为空闲
部分:slab中对象有的被标记为使用,有点被标记为空闲。

当一个slab充满了已使用的对象时,下一个对象的分配从空的slab开始分配。如果没有空的slab,则从物理连续页上分配新的slab,并把它赋给一个cache。

优点

1、没有因碎片而引起的内存浪费
因为每个内核数据结构都有相应的cache,而每个cache都由若干slab组成,而每个slab又分为若干个与对象大小相同的部分。
2、内存请求可以快速满足
由于对象预先创建,所以可以快速分配,刚用完对象并释放时,只需要标记为空闲并返回,以便下次使用。

使用的系统

  • Solaris2.4内核
  • Solaris某些用户态内存请求
  • Linux2.2以后的内核

六、虚拟内存中的其他考虑

预先调页

纯请求分页系统的显著特性是在进程启动初期,会出现大量缺页,这种情况是由于试图将最初局部调入内存的结果,当然,在其他情况下,也可能出现同样的情况。
例如,当重启一个换出进程时,由于所有的页都在磁盘上,每个页都必须通过缺页中断调入内存。预调页策略试图阻止这种大量的初始调页,这种策略就是同时将所有的或所需要的页一起调入内存中。有的操作系统如Solaris,对小文件采用了预调页。对于采用工作集的系统,为每个进程保留一个位于其工作集内的页的列表。如果必须暂停一个进程,那么记住该进程的工作集。当该进程需要重启时,在重启进程前自动调入其工作集中的所有页。

预调页有时性能比较好,关键是采用预调页的成本是否小于处理相应缺页中断的成本。因为如果预调入的页面没有被使用,内存被浪费。

页面尺寸选择

页面大小总是2的幂,通常是4KB~4MB。

  1. 页表大小——需要大的页
    对于给定的虚拟内存空间,降低页大小,就增加了页的数量,因此也增加了页表大小。
    例如,4MB的虚拟内存,对于页的大小为1KB,就有4096个页;对于页大小为8KB,就只有512个页。
    因为每个进程必须有自己的页表,所以较大的页是比较理想的。
  2. 碎片——需要小的页
    因为进程最后一页不可能正好是放满一整页,一般有页内碎片。平均来说,每个进程的最后一页的一半将会被浪费。
    例如,512字节的页,损失256字节;8KB的页,损失为4KB。因此,为了减少碎片,需要小的页。
  3. I/O开销——需要大的页
    I/O时间包括了寻道、延迟和传输时间。尽管传输时间与传输量(即页的大小)成正比,需要小的页。但是,磁盘的寻道时间和延迟时间远远超过传输时间。
    例如,读入一个1KB的页需要28.4ms,而读入一个512字节的页需要56.4ms,因此,为了最小化I/O时间,需要大的页。
  4. 程序局部——需要小的页
    较小的页允许每个页精确匹配程序局部;较大的页不仅必须分配且传输所需要的还包括其他碰巧在页内的不需要的内容。因此,更小的页导致更少的I/O和更少的总的内存分配。
  5. 缺页次数一需要大的页
    由于每个缺页会产生大量的额外开销,为了降低缺页次数,需要较大的页。

还有一些其他因素。没有最佳答案,总的来说,趋向更大的页。
在这里插入图片描述

TLB范围

增加TLB的数目,可增加命中率,这种方法的代价是很大的,因为用于构造TLB的相关内存既昂贵又费电。与命中率相关的另一个测量尺度是TLB范围。

TLB范围是指通过TLB所访问的内存量。
TLB范围=(TLB大小)X(页大小)。
理想情况下,一个进程的工作集应存放在TLB中,否则会有大量的缺页中断。
如果把TLB条数加倍,那么TLB范围就加倍。但是对于某些使用大量内存的应用程序,这样做可能不足以存放工作集。
增加TLB范围的另一种方法是增加页的大小。对于不需要大页的应用程序而言,这将导致碎片的增加。

还有一种是提供多种页的大小。这允许需要大页的应用程序有机会使用大页而不增加碎片的大小。
UItraSPARCA芯片支持8KB,64KB,512KB和4MB大小的页。
对于具有64项的TLB,Solaris的TLB范围可从512KB(使用8KB大小的页),到256MB(使用4MB的页)。
对于大部分程序,8KB大小的页足够了,对于需要大页的应用程序,如数据库,有机会使用大页而不增加碎片的大小。现代趋势是用软件来管理TLB和操作系统提供对多种页大小的支持。

反向页表

反向页表降低了保存的物理内存;
不再包括进程逻辑地址空间的完整信息;
为了提供这种信息,进程必须保留一个外部页表
外部页表可根据需要换进或换出内存

程序结构

1、程序结构可能影响系统性能

  • array A[1024,1024]of integer
  • 每行保存在一页
  • 分配一个页框
  • 程序1:1024×1024缺页
  • 程序2:1024次缺页
    在这里插入图片描述

2、其它因素(编译器、载入器、程序设计语言)对调页都有影响

数据结构和程序结构的仔细选择能够改善系统性能。因为这样的做法能够增加局部性,降低错误率和工作集内的页数。
在这里插入图片描述

I/O互锁

允许某些页在内存中被锁住。

为防止I/O出错,有两种解决方案:
(1)不对用户内存进行I/O。
即I/O只在系统内存和I/O设备间进行,数据在系统内存和用户内存间复制。
(2)允许页锁在内存,锁住的页不能被置换。
即正在进行I/O的页面不允许被置换算法置换出内存。当I/O完成时,页被解锁。

猜你喜欢

转载自blog.csdn.net/ITmincherry/article/details/106811894