程序执行时,通常不需要将整个程序都放在内存中,如此可以获得更大的逻辑空间,更小的物理内存占用。
按需调页
采用按需调页,即在需要时才调入相应的页。页表中有效位为 0 表示该页无效或者在磁盘上。当在磁盘上的页被调用时,系统陷入页错误陷阱,完成调页处理后重新开始当前指令。采用按需调页后,为提高性能,需要降低页错误率。
写时复制
父子首先共享同一页面,当需要进行写操作时,再去创建共享页的副本。写时复制类似可持久化数据结构中常用的那种操作。
页面置换
设计页面置换算法的目标是降低页错误率。我们可以将引用地址序列描述为页号的序列,称为引用串。
常见的页面置换算法中,OPT 算法难以实现因此通常只用来作为对照;FIFO 算法中可能会出现 Belady 异常(随着容量增加页错误率反而降低);LRU 算法通过计数器或栈来实现,但是成本较大;近似 LRU 算法才是如今常采用的;此外还有基于计数的方法,维护引用次数计数器,实现 LFU/MFU。
近似 LRU 算法通常有三类实现:附加引用位算法每隔一段时间生成新的引用位表示这段时间内是否发生引用,同时维护过去几个时间间隔的引用位;时钟算法循环扫描所有条目,如果引用位 = 1 则清零并绕过他继续扫描;增强时钟算法则引入了优先级,给已经修改过的页更高的级别来降低 I/O 的数量。
为了降低牺牲页选择过程引起的时间开销,常维护页缓冲,即一个空闲物理帧的缓冲池,使得牺牲页选择的时间开销可以被隐藏。
帧分配
帧分配决定了如何为各进程分配空闲帧,常用的方法有平均分配和按比例分配。其需要考虑帧数的限制,最小的帧数由体系结构决定,而最大的帧数由实际可用的内存决定。
根据置换页面的来源,页面置换被分为局部置换和全局置换。局部置换的页面只能来源于分配给该进程的帧,而全局置换可以来源于所有进程。
系统颠簸
系统出现频繁的页调度行为,在换页上花费的时间多余执行的时间,称为颠簸。这导致多道程度达到一定值后,CPU 使用率反而下降。
基于局部模型(一个程序由多个不同局部组成),引入工作集合 WS,研究一个进程实际使用多少帧。
另一类方法是直接控制页错误率,当页错误率高于或低于阈值时,调整对该进程的帧分配。
内存映射文件
通过虚拟内存技术,可以将文件 I/O 作为普通内存访问。磁盘块被映射为内存中的若干个页,执行页面调度。类似的还有内存映射 I/O。
内核内存分配
Buddy 内存管理采用了类似线段树的结构,这种方式浪费很大。
slab 是基于对象进行管理的。slab 是物理上连续的若干个页,每个内核数据结构拥有一个 cache,每个 cache 包含若干个 slab,每个 cache 通常含有内核数据结构的对象实例。
其他问题
为了解决进程开始时的大量页错误,引入语调也,同时将所需要的所有页(的一部分)调入内存中。
页大小的选择,通常设置为 2 的整数幂,需要考虑页表大小、内存碎片、读写开销等多种问题。