操作系统笔记:内存虚拟化
Posted CS实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统笔记:内存虚拟化相关的知识,希望对你有一定的参考价值。
程序自身并不需要关心自己的数据及代码存在哪,并且对程序来说,内存看上去是连续且独占的。当然事实肯定不是如此,而这背后就是操作系统的功劳 —— 内存虚拟化。本篇文章就介绍操作系统是如何实现虚拟内存系统的。
地址空间
physical address = virtual address + base
操作系统的工作
操作系统和硬件支持结合,实现了虚拟内存,而为了实现虚拟内存,操作系统所需要做的工作如下:
在进程终止时,操作系统必须回收它的所有内存,给其他进程或者操作系统使用。
在上下文切换时,操作系统必须保存和恢复基址和界限寄存器。具体的说,操作系统必须将当前基址和界限寄存器中的内容保存在内存中,放在某种每个进程都有的结构中,如进程结构或进程控制块中;当操作系统恢复执行某个进程时,也必须给基址和界限寄存器设置正确的值。
操作系统必须提供异常处理程序。
分段
为了解决连续内存的浪费问题,操作系统引入了分段。
操作系统的问题
分段带来一些新的问题。
第二个也是更重要的问题是分段会带来外部碎片。空闲空间被分割成不同大小的小块,成为碎片,后续的请求可能会失败,因为没有一块足够大的连续空闲空间,即使这时总的空闲空间超出了请求的大小。
解决外部碎片的一种方法是紧凑物理内存,重新安排原有的段,但内存紧凑成本很高;另一种简单的方法是使用空闲列表(free-list)管理算法,试图保留大额内存用于分配。目前已经有数百种方法,包括经典算法:
最优匹配 (best fit):首先遍历整个空闲列表,找到和请求大小一样或更大的空闲块,然后返回这组候选者中最小的一块。只需要遍历一次空闲列表,就足以找到正确的块并返回。然而,简单的实现在遍历查找正确的空闲块时,要付出较高的性能代价。
最差匹配 (worst fit):与最优匹配相反,它尝试找最大的空闲块,分割并满足用户需求后,将剩余的块(很大)加入空闲列表。最差匹配尝试在空闲列表中保留较大的块,而不是向最优匹配那样可能剩下很多难以利用的小块。但是,最差匹配同样需要遍历整个空闲列表。更糟糕的是,大多数研究表明它的表现非常差,会导致过量的碎片,同时还有很高的开销。
首次匹配 (first fit):首次匹配策略就是找到第一个足够大的块,将请求的空间返回给用户。同样,剩余的空闲空间留给后续请求。首次匹配有速度优势,但有时会让空闲列表开头的部分有很多小块。
下次匹配 (next fit):多维护一个指针,指向上一次查找结束的位置。其想法是将对空闲空间的查找操作扩散到整个列表中去,避免对列表开头频繁的分割。与首次匹配很接近,同样避免了遍历查找。
还有一些改进策略:
分离空闲列表:如果某个应用程序经常申请一种(或几种)大小的内存空间,那就用一个独立的列表,只管理这样大小的对象。其他大小的请求都交给更通用的内存分配程序。
伙伴系统:空闲空间首先从概念上被看成大小为 2N 的大空间。当有一个内存分配请求时,空闲空间被递归地一分为二,直到刚好可以满足请求的大小(再一分为二就无法满足);如果将这个8KB的块归还给空闲列表,分配程序会检查“伙伴”8KB是否空闲。如果是,就合二为一,变成16KB的块。
然而不管算法多么精妙,外部碎片仍然存在,无法完全消除。唯一真正解决的办法就是完全避免这个问题,永远不要分配不同大小的内存块,这也是分页被引入的原因。
分页
页表
分页的瓶颈
分页虽然看起来是内存虚拟化需求的一个很好的解决方案,但这两个关键问题必须先克服。
分页和分段结合
这种杂合方案的关键区别在于,每个分段都有界限寄存器,每个界限寄存器保存了段中最大有效页的值。例如,如果代码段使用前3个页,则代码段页表将只有3个项分配给它,并且界限寄存器将被设置为3。与线性页表相比,杂合方法实现了显著的内存节省,栈和堆之间未分配的页不再占用页表中的空间 (仅将其标记为无效)。
而这种方法的弊端在于,一是它仍然要求使用分段,如果有一个大而稀疏的堆,仍然可能导致大量的页表浪费;二是外部碎片再次出现,尽管大部分内存是以页表大小单位管理的,但页表现在可以是任意大小 (PTE 的倍数)。
多级页表
多级页表也是用来解决页表占用太多内存的问题,去掉页表中的所有无效区域,而不是将它们全部保留在内存中。多级页表将线性页表变成了树。
首先,将页表分成页大小的单元;然后,如果整页的页表项 (PTE) 无效,就完全不分配该页的页表。为了追踪页表的也是否有效 (以及如果有效,它在内存中的位置),使用了名为页目录的新结构。页目录可以告诉你页表的页在哪里,或者页表的整个页不包含有效页。
在一个简单的两级页表中,页目录为每页页表包含了一项。由多个页目录项 (PDE) 组成,PDE 至少拥有有效位 (valid bit) 和页帧号 (PFN),类似于 PTE。但这个有效位的含义稍有不同:如果 PDE 项是有效的,则意味着该项指向的页表 (通过 PTE) 中至少有一页是有效的,即在该 PDE 所指向的页中,至少一个 PTE,其有效位被设置为 1。如果 PDE 项无效,则 PDE 的其余部分没有定义。
多级页表的好处
如果仔细构建,页表的每个部分都可以整齐的放入一页中,从而更容易管理内存。有了多级页表,我们增加了一个间接层,使用了页目录,指向页表的各个部分,这种间接方式,让我们能够将页表页放在物理内存的任何地方。
多级页表的缺点
另一个明显的缺点是复杂性。无论是硬件还是操作系统来处理页表查找,这样做无疑都比简单的线性页表查找更复杂。
TLB
基本算法
如果没有 (TLB 未命中),在不同的系统中表现不一样:
硬件管理 TLB (旧体系结构,如 x86):发生未命中时,硬件会遍历页表,找到正确的页表项,取出想要的转换映射,用它更新 TLB。
软件管理 TLB (更现代的体系结构):发生未命中时,硬件系统会抛出一个异常,暂停当前的指令流,将特权级提升至内核模式,跳转至陷阱处理程序 (操作系统的一段代码)。接下来这段陷阱处理程序会查找页表中的转换映射,然后用特别的 “特权” 指令更新 TLB,并从陷阱返回。此时,硬件会重试该指令 (导致 TLB 命中)。
上下文切换时对 TLB 的处理
简单地清空 TLB
如果是软件管理 TLB 的系统,可以在发生上下文切换时,通过一条显式指令来完成;如果是硬件管理 TLB 系统,则可以在页表基址寄存器内容发生变化时清空 TLB。不论哪种情况,情况操作都是把全部有效位置为 0,本质上清空了 TLB。
但该方法有一定开销:每次进程运行,当它访问数据和代码页时,都会触发 TLB 未命中,如果操作系统频繁切换进程,这种开销会很高。
跨上下文切换的 TLB 共享
交换空间
在硬盘上开辟一部分空间用于物理页的移入和移出,在操作系统中这样的空间称为交换空间,因为我们将内存中的页交换到其中,并在需要的时候又交换回去。因此,我们会假设操作系统能够以页为大小为单元读取或者写入交换空间,为了达到这个目的。
存在位
硬件通过页表中的存在位,来判断是否在内存中。如果存在位设置为1,则表示该页存在于物理内存中,并且所有内容都正常进行;如果存在位设置为0,则页不在内存中,而在硬盘上。
页错误
访问不在物理内存中的页,这种行为通常被称为页错误。这时 “页错误处理程序” 被执行,处理页错误。
处理页错误的流程:
如上图所示,当操作系统接收到页错误时,会先找可用的物理帧,如果找不到,操作系统会执行交换算法,踢出一些页,释放物理帧,并将请求发送到硬盘,将页读取到内存中。
交换发生时间
为了保证有少量的空闲内存,大多数操作系统会设置高水位线 (HW) 和低水位线 (LW)。
原理是:当操作系统发现有少于 LW 个页可用时,后台负责释放内存的线程会开始运行,直到有 HW 个可用的物理页。这个后台线程有时称为交换守护进程 (swap daemon) 或页守护进程 (page daemon),然后会进入休眠状态。
交换策略
当内存不够时,由于内存压力迫使操作系统换出一些页,为常用的页腾出空间,确定要踢出哪些页封装在操作系统的替换策略中。交换策略有很多,如下:
最优交换策略
最优替换策略能达到总体未命中数量最少,即替换内存中在最远将来才会被访问到的页,可以达到缓存未命中率最低。但很难实现。
简单策略 (FIFO)
FIFO 策略,即页在进入系统时,简单地放入一个队列,当发生替换时,队列尾部的页被踢出。
FIFO 有个很大的优势:实现相当简单。但其根本无法确定页的重要性,即使页 0 已被多次访问, FIFO 仍然会将其踢出。且 FIFO 会引起 Belady 异常。
最少最近使用 (LRU)
LRU 策略是替换最近最少使用的页。
LRU 目前看来优于 FIFO 策略及随机策略,但随着系统中页的数量的增长,扫描所有页的时间字段只是为了找到最精确最少使用的页,这个代价太大。
时钟算法 (Clock)
Clock 算法是近似 LRU 的一种算法,也是许多现代系统的做法。该算法需要硬件增加一个使用位。
过程:
系统中的所有页都放在一个循环列表中,时钟指针开始时指向某个特定的页;
当必须进行页替换时,操作系统检查当前指向的页 P 的使用位;
如果为 1,则意味着页 P 最近被使用,不适合被替换,然后将其设置为 0,时钟指针递增到下一页 (P + 1);
一直持续到找到一个使用位为 0 的页;
最坏的情况下,所有的页都被使用了,那么就将所有页的使用位都置为 0。
考虑到内存中的页是否被修改,硬件增加一个修改位。每次写入页时都会设置此位,因此可以将其合并到页面替换算法中。如果页已被修改并因此变脏,则提出就必须将它写回磁盘,这很昂贵;如果没有被修改,踢出就没有成本。因此,一些虚拟系统更倾向于踢出干净页,而不是脏页。
总结
本文就操作系统的内存虚拟化部分做了简单总结,包括分段、分页、TLB 以及交换空间。通过这些,操作系统实现了虚拟内存系统,从而保证内存对程序的透明,程序访问内存的高效,以及进程之间的相互隔离。
本文参考《操作系统导论》
以上是关于操作系统笔记:内存虚拟化的主要内容,如果未能解决你的问题,请参考以下文章