硬件和控制结构
实际内存管理特点
- 一个进程可以在执行过程中换入换出内存,因而在内存中的位置可以不断变化。
- 一个进程可以划分为多个块,这些块位于内存中的地址不需要是连续的。
进程执行的任何时候都处于内存中的部分称为常驻集(resident set)。
内存管理提高系统利用率
- 在内存中保留多个进程。因为每个进程只需要它的一部分位于内存中,内存可以容纳更多的进程,更多的进程有更大的概率使得任何时刻至少有一个进程处于就绪态,从而处理器的利用率得到提高。
- 进程可以比内存的全部空间大。进程只需要它的一部分调入内存就可以得到执行,其大小不完全受内存大小的限制。
虚存支持更有效的系统并发度。
一个进程的部分进程块位于内存中,该进程执行,当访问的进程块(此处的进程块表示为某个进程划分成的多个页或者段)不在内存中时,向 OS 发出中断信号,该进程转为阻塞态,处理器交由其他进程使用,同时向外存发出读请求,调入该进程需要访问的进程块。当调入完成时,该进程转为就绪态。
系统抖动(thrashing)是指处理器的大部分时间用于交换块而不是在执行指令的现象。
一个进程有一个属于自己的页表,每个页表项记录页号对应的页框号。当使用虚存时,需要有多个控制位实现一些功能,其中最重要的两个功能是标记该页是否位于内存中和该页是否被修改的标志位。是否位于内存中决定了进程正常执行还是中断,是否被修改决定换出时直接移动到外存还是覆盖原有数据写入到外存。
页表大小由进程大小决定。一般情况下,页表保存在内存中;当页表很大时,保存在外存中。页表特别大时,可能采用两级层次的页表。根页表是页表目录,保存每个页表的地址,找到特定的页表后,根据页表找到对应的页框地址。
虚拟地址中的页号经过一个散列函数映射到一个散列表中一项,该项有一个指针指向一个倒排页表(该页表中每项大小等于一个页框)中的一项,该项包含页框号、进程标识符等信息。
一般情况下,虚存的内存访问实际上执行了两次,一次是对页表的访问,一次是对真正数据的访问。为了克服此问题,提出了转换检测缓冲区(translation lookaside buffer TLB)。该机制类似于 CPU 的高速缓存,当命中时直接获得对应的页框号,没命中时访问内存中的页表。
真实的单次内存访问有多种情况。虚拟地址转成物理地址时,访问页表项,页表项可能在 TLB、内存或者外存中;之后访问实际内存数据,该数据可能在高速缓存、内存或者外存中。根据具体情况采取不同的措施。
页本身的大小增加,可能产生的内部碎片增加;大小减少,一个进程的页的数量增加,页表大小随之增加,可能访问一次内存产生两次缺页中断。
在其他条件不变的情况下,缺页率随着页的大小增大先上升后减小。在所有进程页总数及其他条件不变的情况下,缺页率随着分配给页的页框的增加而降低,先快速降低后缓慢降低。
内存的段式组织形式和非段式组织形式相比,优点有
- 简化了对可动态增大的数据结构的处理。
- 允许程序独立改变或者重新编译,不需要整个数据集重新链接和加载。
- 有助于进程间加载。
- 有助于保护。
在虚存分段的形式中,每个进程都有一个段表,每一项存放段号对应的段首地址和段的长度、控制信息等。控制信息主要包括是否位于内存中、是否被修改过等标志位信息。
在段页式系统中,结合了两种方式。首先内存空间划分成了多个段,每个段的大小为页大小的整数倍。地址转换时先找到对应的段,再找到段中对应的页首地址,最后加上偏移量组成实际地址。
OS 软件
OS 的内存管理设计涉及的三个方面
- 是否使用虚存技术。
- 使用分页、分段或者两者结合的方式(由硬件支持)。
- 内存管理使用的各种算法。
读取策略决定某页何时调入内存。
读取策略分两种方式:
- 请求分页(demand paging):当访问到某页中的一个单元时才将该页调入内存中。
- 预先分页(prepaging):若一个进程多个页连续存储在外存中,一次性将其成批调入到内存中。由于局部性原理,一般情况下,这种方式比一次调入一页的效果好。
放置策略决定一个进程块放置在内存中哪个位置。
在非一致性存储访问多处理器(nonuniform memory access, NUMA)中,机器中的共享内存可由多个处理器访问,访问花费的时间根据处理器和内存模块之间距离的不同而变化,性能很大程度上取决于距离。
置换策略:内存中所有页框都被使用,此时需要调入一个新页响应缺页中断时,决定置换当前内存中的哪一页。目标是调出最近最不可能访问的页。
页框锁定:内存中的某些页框可能是被锁定的,不允许被置换。
置换的基本算法:
- 最佳(Optimal, OPT)策略选择置换下次访问距离当前时间最长的页。此方法不可能实现。
- 最近最少使用(Least Recently Used, LRU)策略置换内存中最长时间没有被使用的页。难以实现,实现的系统开销非常大。性能仅次于 OPT。
- 先进先出(FIFO)策略将分配给进程的页框看作一个循环缓冲区,按照循环方式移动页。实现最简单,性能最差。
- 时钟策略(clock policy)为每个页框关联一个标志位,标志位默认为 0。当该页框有页写入其中,立刻将关联的标志位置为 1。当需要调入新页时,将可能被置换的页框看作是一个循环缓冲区,当某个页框被使用时,有一个指针指向被使用的页框的下一个页框,一直循环。OS 从开头循环寻找第一个标志位为 0 的页框,置换其中的页;当标志位为1时,将其置为 0。最差情况在第二轮寻找时可找到置换的页进行置换操作。该策略性能优于 FIFO,比 LRU 差。
改进的时钟策略是给页框关联两个标志位:一个标志位(access)记录页框中的页是否最近被访问,另一个标志位(modify)记录页框中的页是否被修改过。0 表示否,1 表示是。算法如下:
- 寻找第一个 access=0,modify=0 的页框,找到则置换。否则
- 寻找第一个 access=0,modify=1 的页框,跳过的页框将其 access = 0。若找到,则置换。否则
- 此时所有页框的 access = 0。重复第一步,必要时重复第二步必然可以找到可置换的页。
上述方法优点是节省了 I/O 的时间。
页缓冲:在 FIFO 的策略下,内存中构建两个链表:一个是空闲页链表,前几块必然处于空闲状态;另一个是修改页链表。当置换时,被调出的页如果没有被修改,则放入空闲页链表尾部,当再次访问该页时直接放入之前的驻留集中;否则放入修改页链表的尾部,在某个时间点将修改页链表中的页成批地写入到外存中。
驻留集大小:决定给特定的进程分配多大的内存空间。
驻留集分配的相关因素
- 分配给一个进程的内存越少,内存中进程的数量越多。
- 进程的页数在内存中较少,则缺页率相对较高。
- 给特定进程分配的内存空间超过一定大小后,缺页率变化不大。
OS 采用的分配策略
固定分配策略:给每个进程分配固定数量的页框使用。
可变分配策略:每个进程在内存中可使用的页框数量是变化的。
置换范围策略
局部置换策略:在产生缺页中断的进程的驻留页中选择被替换的页。
全局置换策略:在整个内存空间的未被锁定页中选择被替换的页。
固定分配、局部范围:缺点是页数过少,缺页率高;过多,处理器时间浪费。
可变分配、全局范围:容易实现。难点在于置换页选择。
可变分配、局部范围:1.新进程进入内存时,分配一定数量页框并填满;2.发生缺页中断时,从该进程的驻留页中选择一页置换;3.定期评估页框分配情况,进行动态调整。
工作集表示在某个时间点时的驻留集。
工作集的变化周期是瞬变(缺页率高),稳定(缺页率低)两种情况交替出现。
若一个进程的缺页率低于某个最小阈值,则可以给该进程分配一个较小的驻留集但不降低性能,使得整体受益;若缺页率高于某个最大阈值,在不降低整个系统的性能情况下增大其驻留集,使得该进程受益。
缺页中断频率(Page Fault Frequency, PFF)算法:每页关联一个使用位。某页被访问时,关联的使用位置 1;发生一次缺页中断时,OS 记录该进程从上次发生缺页中断到目前为止的时间。定义一个阈值 F,若该时间小于F,则将该页加入到驻留集;否则去除所有使用位为 0 的页,缩减驻留集大小,同时将其余页的使用位置 0。算法缺点是转移到新稳定状态的过渡阶段时(即瞬变阶段),执行效果不好。
为了改进此缺点,使用可变采样间隔的工作集(variable-interval sampled working set, VSWS)策略:定义一个时间区间。在区间开始阶段,所有驻留集中的页使用位置 0;在区间末尾时,在此区间中被访问过的页使用位置 1,同时这些页在下一个区间保留在驻留集中,其他页从驻留集中去除。
清除策略用于确定何时将已修改的一页写回外存。
请求式清除:某页被选择置换时写回外存。
预约式清除:将已被修改的页在使用它们之前成批地写回外存。
系统并发度:驻留在内存中的进程数量。
L=S 准则:通过调整系统并发度,使缺页中断之间的平均时间等于处理一次缺页中断花费的平均时间。
50%准则:系统利用率保持在50%。
监视时钟算法中扫描页框的指针循环缓冲区的速度。当速度低于某一阈值时,
1.很少发生缺页中断
2.扫描的平均页框数量很少。
此时,系统并发度可以增大。速度高于某一阈值时,
1.缺页率很高。
2.难找到可置换页。
进程被挂起时选择被挂起进程的策略
- 最低优先级进程。
- 缺页中断进程。
- 最后一个处于激活状态的进程。
- 驻留集最小的进程。
- 驻留集最大的进程。
- 驻留集中空闲页框数量最多的进程。
参考
[1] William Stallings, 操作系统——精髓与设计原理(8th), 2017.