OS之内存管理 --- 虚拟内存管理(二)

关于虚拟内存管理之前的请看:OS之内存管理 — 虚拟内存管理(一)

帧分配

每个进程对的最小帧数是由操作系统的体系结构决定的,但是最大帧数是由可用物理内存的数量决定的。所以在这之间,对于进程的帧的分配是有很多选择的。

分配算法

  • 平均分配
    在n个进程中分配m个帧的最容易的方法就是,给每个进程一个平均值,即m/n帧(在这里是忽略操作系统所需的帧,即m个帧是空闲帧)。比如有93个帧和5个进程,那么分配给每个进程18个帧,剩余的3个帧可以用作空闲帧缓冲池,这种方法称为平均分配

  • 比例分配
    因为进程大小不同,如果平均分配的话,小进程分配的帧数太多,浪费资源;大进程分配帧太少,不够用。为了解决这个问题,采用比例分配。根据每个进程大小分配可用内存。假设进程pip_i的虚拟内存大小为sis_i,并且定义:S=si (S)S=\sum{s_i} (S为系统空闲内存总量)
    如果可用帧的总数为m,那么进程pip_i可以分配得到aia_i个帧,aia_i近似为:
    ai=si/Sma_i=s_i / S * m

对于平均分配和比例分配,每个进程分得的数量可以因多道程度而变化。如果多道程度增加,则每个进程会失去一些帧,以提供给新进程所需的内存;如果多达程度降低,那么原来分配给离开进程的帧会分配给剩余进程。
在这里需要注意:对于平均分配和比例分配,高优先级进程和低优先级进程同样处理。但是希望给予高优先的进程更多的内存以便加速执行,一种解决方法是:所采用的比例分配的策略不是根据进程的相对大小,而是根据进程的优先级或大小和优先级的结合。

全局分配和局部分配

为个进程分配帧的一个重要因素是页面置换。由于多个进程竞争帧,可以将页面置换算法分为两大类:全局置换局部置换
全局置换允许一个进程从所有帧的集合中选择一个置换帧,而不管该帧是否已分配给其他进程,也就是说,一个进程可以从另外一个进程那里获取帧。
局部置换要求每个进程只从它自己分配的帧中选择。

那么就有一种帧的分配方案:允许高优先级的进程从低优先级进程中选择帧用于置换。采用局部置换策略,分配给每个进程的帧数不变;采用全局置换,一个进程可能从分配给其他进程的帧中选择一个用于置换,从而增加了分配给它的帧数。
但是如果采用全局置换的分配方案,那么进程不能控制自己的缺页错误率。因为一个进程的内存页面的集合不仅取决于进程本身的调页行为,还取决于其他进程的调页行为。
通常情况下,局部置换由于不能使用其他进程的较少使用的内存页面,可能会阻碍一个进程,而全局置换通常会有更好的系统吞吐量,所以还是在很多系统中经常使用的。

非均匀内存访问

之前对于虚拟内存的讨论都是在假设所有内存都相同或可以被平等访问的基础上。但是现代计算机一般情况下都是“多核”或者“多芯”。对于具有多个CPU的系统,给定的CPU可以比其他CPU更快的访问内存的某些部分。这些性能上的差异主要是因为CPU和内存在系统中互连造成的,在这里不做深究。
对于特定CPU访问同板内存的延迟,要小于访问其他板内存的延迟,这种具有明显不同内存访问时间的系统称为非均匀内存访问系统(NUMA)。那么在考虑NUMA中帧分配时,要考虑让分配的帧"尽可能的靠近"运行进程的CPU。这通常意味着与CPU一样位于同一系统板。
对于帧分配算法的修改应该让调度程序跟踪每个进程运行的最后一个CPU,如果调度程序尝试将每个进程调度到它先前的CPU上,而且内存管理系统尝试分配的帧靠近正在分配的CPU,那么将导致高速缓存命中率的改进和内存访问时间的减少。

系统抖动

如果进程没有需要支持活动使用页面的帧数,那么它会很快产生缺页错误,这时必须要置换某个页面。但是由于它的所有页面都在使用中,那么必须立即置换需要再次使用的页面。因此它会再次快速产生缺页错误,再一次置换必须立即返回的页面,如此快速进行。这种高度的页面置换活动叫做抖动如果一个进程的调页时间多于执行时间,那么进程就在抖动

解决抖动的方法

通过局部置换算法优先权置换算法,可以限制系统抖动。如果一个进程开始抖动,那么由于采用局部置换,它将不能从另外进程中获取帧。
注意:这种方法只是限制,并不能解决抖动。因为发生了抖动,那么大多数时间内会排队等待调页设备,即使对于不在抖动的进程,也会影响其性能。

工作集模型

为了防止抖动,需为进程提供足够多的所需帧,但是怎么样才能知道进程需要多少帧呢?有一种技术叫做工作集模型
这种方法定义了进程执行的局部性模型:局部性模型指出随着进程执行,它从一个局部移向另一个局部。局部性是最近使用页面的一个集合。一个程序通常由多个不同的可能重叠的局部组成。
工作集模型就是基于局部性假设的。这个模型采用参数Δ\Delta,定义工作集窗口,其思想就是检查最近Δ\Delta个页面引用。这最近Δ\Delta个页面引用的页面集合称为工作集。如果一个页面处于活动使用状态,那么它处在工作集中。如果它不再使用,那么它在最后一次引用的Δ\Delta时间单位后,会从工作集中删除。所以,工作集是程序局部的近似。
工作集的精度取决于Δ\Delta的选择,如果Δ\Delta太小,那么他不能包含整个局部;如果Δ\Delta太大,那么他可能包含多个局部,在极端情况下,如果Δ\Delta为无穷大,那么工作集为进程执行所需所有页面的集合。所以最重要的工作集属性就是它的大小。
如果系统内的每个工作集通过计算为WSSiWSS_i,那么会得到:
D=ΣWSSiD=\Sigma{WSS_i}
这里的D就是帧的总需求量,每个进程都使用其工作集内的页面,所以进程i需要WSSiWSS_i帧,如果总需求量大于可用帧的总数(D>m),将会发生抖动,因此有些进程得不到足够的帧数。
一旦选中了Δ\Delta,工作机的使用就是由操作系统监视每个进程的工作集,并为它分配大于其工作集的帧数。如果还有足够的额外帧,那么可启用另一进程。如果工作集大小的总和增加,以至于超过可用帧的总数,那么操作系统会选择一个进程来挂起。该进程的页面被写出(交换),并且其帧可分配给其他进程。挂起的进程以后可以重启。

缺页错误频率

工作集模型的控制是比较笨拙的,因为系统需要监控每个进程的工作集模型,然后做出相应的策略。采用**缺页错误频率的策略(PFF)**是更加直接的方法。
缺页错误频率策略设置所需缺页错误率的上下限,如果实际缺页错误率超过上限,则可为进程在分配一帧;如果实际缺页错误率低于下限,则可从进程中删除一帧。通过直接测量和控制缺页错误率以防止抖动。
与工作集策略相同,也可能不得不换出一个进程,如果缺页错误率增加并且没有空闲帧可用,你那么必须选择某个进程并将其交换到后备存储,然后再将释放的帧分配给具有高缺页错误率的进程

分配内核内存

用于内核内存的空闲内存池通常不同于用于普通用户模式进程的列表,主要有两个原因:

  1. 内核需要为不同大小的数据结构请求内存,其中有的小于一页,因此内核应该保守的使用内存,并努力最小化碎片的浪费
  2. 用户模式进程分配的页面不必位于连续物理内存,但是有的硬件设备和物理内存直接交互,即无法享有虚拟内存接口的便利。

为了管理用于内核进程的空闲内存,有两种策略:伙伴系统和slab分配

伙伴系统

伙伴系统从物理连续的大小固定的端上进行分配,从这个段上分配内存,采用2的幂分配器来满足请求分配单元的大小为2的幂,请求单元的大小如果不适当,那么就调整到下一个更大的2的幂。
假设内存段的大小最初为256kb,内核请求21kb的内存,最初这个段分为两个伙伴,称为ALA_LARA_R,每个的大小都是128kb;这两个伙伴之一进一步分成两个64kb的伙伴,即BLB_LBRB_R。然而从21kb开始的下一个打的幂是32kb,因此BLB_LBRB_R再次划分为两个32kb的伙伴CLC_LCRC_R。因此其中一个32kb的段可用于满足21kb请求。
伙伴系统的优点是:通过称为合并的技术,可以将相邻伙伴快速组合已形成更大的分段。
缺点:由于调整到下一个2的幂,很可能造成分配段内的碎片

slab分配

每个slab由一个或多个物理连续的页面组成。每个cache由一个或多个slab组成。每个内核数据结构都有一个cache。每个cache含有内核数据结构的对象实例(称为object),三者关系如下:
在这里插入图片描述
slab分配算法采用cache来存储内核对象。在创建cache时,若干起初标记为free的对象被分配到cache。cache内的对象数量取决于相关slab的大小。例如12kb slab(由三个连续的4kb页面组成)可以存储6个2kb的对象。最初cache内的所有对象都标记为空闲,当需要内核数据结构的新对象时,分配器可以从cache内分配任何空闲对象以便满足请求。
在Linux中,slab可以处于三种可能状态之一:

  • 满的(full):slab的所有对象标记为使用
  • 空的(empty):slab上的所有对象标记为空闲
  • 部分(partial):slab上的对象有的标记为使用,有的标记为空闲

slab分配器有两个优点:

  • 没有因碎片而引起内存浪费
  • 可以快速的满足内存请求
参见于:《操作系统概念
posted @ 2019-04-08 19:16  如是说  阅读(452)  评论(0编辑  收藏  举报