存储管理之分页原理与算法
存储管理器:记录哪些内存是正在使用的,哪些内存是空闲的;在进程需要时为其分配,在使用完之后释放。
1. 无存储器抽象
这是一种将地址暴露给进程使得直接操作内存的方法,无存储器抽象下运行多道程序的尝试:IBM 360,内存被划分为2K块,每个块被分配一个4位的保护键,通过PSW(Program Status Word,程序状态字)来保护内存,PSW中一个4位码,程序只能访问保护键与PSW四位码相同的内存块,否则操作系统会捕捉到非法访问这一事件。只有操作系统能修改保护键。带来的问题是重定向问题,程序都引用了绝对物理地址。
多道程序运行时的问题在于都引用了绝对地址,IBM 360的解决方案是采用“静态重定向”技术,比如当程序加载到地址16384时,常数16384被加到每一个程序地址上,但是这种方法有两个弊端,1.加载时会影响装载速度,2.装载器需要一定的方法来区分地址和常数。
由此可见,无存储抽象面临着亮点难题:保护与重定向。
2. 存储器抽象-地址空间
2.1地址空间概念
地址空间为程序创建了一个抽象的内存,地址空间是一个进程可用于寻址的一套地址集合,每个进程都有一个自己的地址空间,并且这个地址独立于其他进程的地址空间(除了在一些特殊情况下进程还需要共享他们的地址空间外)。
地址空间解决了安全的问题:避免一个进程访问另一个进程的地址。
2.2 基址寄存器与界限寄存器
动态重定位:把每个进程的地址空间映射到物理内存的不同部分,当使用基址寄存器和界限寄存器时,程序装载到物理内存时不需要重定位;当一个进程运行时,程序的起始物理地址装载到基址寄存器,程序长度装载到界限寄存器。
每次一个进程访问内存时,取一条指令,读或写一个数据字,CPU硬件会把地址发送到内存总线之前自动把基址寄存器的值加到进程发出的地址值上,同时检查提供的地址是否等于或大于界限寄存器里的值,如果超过界限,则产生错误并中止访问。
2.3 交换技术
交换(swapping)技术即是,把一个进程完整调入内存,使进程运行一段时间,然后把它存回磁盘。空闲进程主要存在于磁盘上,所以它们不运行时就不会占用内存,
2.3.1 空闲空间管理
2.3.1.1 位图管理
使用位图方法,内存被划分成小到几个字或大到几千字节的分配单元,每个存储单元对应于位图中的一位,0表示空闲,1表示占用(或者相反)。
在决定把一个占k个分配单元的进程调入内存时,存储管理器搜索位图,在位图中找到有k个连续0的串。查找位图中指定长度的连续0串是耗时的操作,这是位图的缺点。
2.3.1.2 使用链表管理
维护一个记录已分配内存段和空闲内存段的链表,每个链表节点或者包含一个进程,或者是两个进程间的空闲区域,链表每个节点都包含以下域:空闲区(H)或进程(P)的指示标志、起始地址、长度或指向下一节点的指针。
链表管理的分配算法包括:首次适配(first fit)、下次适配(next fit)、最佳适配(best fit)和最差适配(worst fit);
如果为进程和空闲区维护各自独立的链表,上述四个算法速度都能提高,但是代价是增加复杂度和内存释放速度变慢。
2.4 虚拟内存
交换技术面临这个一个问题:“软件膨胀”,内存不足以装下程序;
虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块被称为一页或页面(page)。每一页有自己的地址范围。这些页被映射到物理内存。
虚拟内存适合多道程序设计,许多程序的片段同时保存在内存中,当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。
2.4.1 分页
对虚拟内存进行管理的工具是‘内存管理单元’(Memory Management Unit, MMU),当虚拟地址被送到MMU,MMU把虚拟地址映射为物理内存地址。
MMU通常作为CPU的一部分,不过从逻辑上看,它可以是一片单独的芯片;但是逻辑上,它可以是一个单独的芯片,并且早就是这样了。
虚拟地址空间按照固定大小划分成‘页面’(page)的若干单元;在物理内存中与之对应的单元被称为‘页框’(page frame),页面和页框大小通常一样。
RAM与磁盘之间的交换总是以整个页面为单元进行。
原理演示:虚拟地址8192(二进制是0010000000000100),输入的16位虚拟地址分为4位的页号和12位的偏移量(计算方法:根据页面大小4K需要12位来确定页内偏移,剩下4位作为页面号码)。
用页号作为也表的索引,得出对应于该虚拟号的页框号,并且用一个标志位表示该页面是否存在于页框中。
2.4.2 页表
虚拟地址被分成虚拟页号(高位部分)和偏移量(低位部分),页表的作用是把虚拟页面映射为页框。
“在/不在”位:这一位是1时表示该表项是有效的,可以使用;如果是0,表示该表项对应的虚拟页面现在不在内存中,访问该页面会引起一个缺页中断。
“保护(protection)位”:指出页面允许什么类型的访问;最简单的使用一个位表示,0表示读/写,1表示只读;另一种是用三位表示,各个位分别表示读、写、执行该页面。
“修改(modified)位和访问(referenced)位”:在写入一页时由硬件自动设置修改位,操作系统在重新分配页框时,如果修改位被设置,则该页是脏的,必须被写回到磁盘,若该位没被设置,则该页框是干净的,只需要简单的丢弃即可。
“高速缓存禁止位”用来保证硬件从设备中读取数据而不是访问一个旧的被高速缓存的副本;但是具有独立IO空间而不使用内存映射IO的机器不需要这一位。
虚拟内存本质上是用来创造一个新的抽象概念-地址空间,虚拟内存的实现是将地址空间分解成页,并将每一页映射到物理内存的某个页框或者(暂时)解除映射。
2.4.3 加速分页过程
分页系统的设计需要考虑的两个问题:1)虚拟地址到物理地址的映射必须非常快;2)如果虚拟地址空间非常大,页表也非常大。
2.4.3.1 转换检测缓冲区
页表一般位于内存中,可以为计算机设置一个小型的硬件设备,将虚拟地址直接映射到武林地址,而不必再访问页表,这种硬件设备被称为转换硬件缓冲区(Translation Lookaside Buffer, TLB),这是利用了局部性原理。
2.4.3.2 软件TLB管理
TLB表项被操作系统显式的装载,当生成一个TLB失效并将问题交给操作系统解决。系统必须先找到该页面,然后从TLB中删除一个表项,接着装载一个新的项,最后再执行先前出错的指令。
当一个页面访问在内存中而不在TLB中时,将产生软失效(soft miss),此时只需要更新一下TLB,不需要产生磁盘IO;当页面本身不在内存中时,将产生硬失效,此时需要一次磁盘存取以装入该页面。
2.4.4 针对大内存的页表
引入TLB的目的是加速虚拟地址到物理地址的转换,另一个问题是如何处理巨大的虚拟地址空间。
2.4.4.1 多级页表
32位地址被划分成10位的PT1域、10位的PT2域和12位的Offset(偏移量)域。
引入多级页表的目的是避免把全部页表一直保存在内存中,特别是那些从不需要的页表就不应该保留。
这个虚拟地址空间超过100万个页面,实际上只需要四个页表:顶级页表以及0~4M(正文段)、4M~8M(数据段)和顶端4M(堆栈段)的二级页表,顶级页表中1021个表项的“在/不在”位都被设为0,当访问它们时将产生缺页中断。
2.4.4.2 倒排页表
在实际内存中,每一个页框有一个表项,而不是每一个虚拟页面一个表项。倒排页表的不足之处是:从虚拟地址到物理空间的转换非常困难,当进程n要访问虚拟内存p时,必须搜索整个倒排列表查找一个表项(n,p),并且对每一次内存操作都要执行一次。
解决方案:使用TLB加速,对于TLB失效的情况,建立一张散列表,用虚拟地址来散列,通过链表处理冲突。
2.5 页面置换算法
页面置换就是调出不常使用的页面来腾出空间,为新的页面提供空间。
存在的问题:当需要从内存中换出某个页面时,它是否只能是缺页进程本身的页面?这个要换出的页面是否可以属于另外一个进程?
2.5.1 最优页面置换算法
在缺页中断发生时,有些页面在内存中,其中一个页面很快将被访问,其它页面可能要到10、100或者1000条指令后才被访问,每个页面都可以用在该页面首次被访问所要前所要执行的指令数作为标记,它应该置换标记最大的页面。
缺点:无法被实现,因为无法预知各个页面中下一次将在什么时候被访问;虽然可以通过仿真程序跟踪一次页面访问情况,但是也只是针对某个具体的程序和输入数据。
2.5.2 最近未使用页面置换算法
系统为每个页面设置了两个状态位,R位:页面被访问时(读或写)设置,M位:当页面(即修改页面)被写入时设置。
工作原理:启动进程时,所有页面两个状态位都被设置为0,一旦页面被访问,发生中断,设置R位,修改页表项,并设置为READ ONLY模式,R位被定期清零,当发生修改时,产生中断,设置页面M位,并将页表项改为READ/WRITE模式。根据R位和M位的组合分为以下4类:
第0类:没有被访问,没有被修改。
第1类:没有被访问,已被修改。
第2类:已被访问,没有被修改。
第3类:已被访问:已被修改。
NRU(Not Recently Used,最近未使用)算法随机从编号最小的非空类中选择一个页面淘汰。
优点:易于理解,能够被有效的实现,虽然性能不是最好的,但是够用。
2.5.3 先进先出页面置换算法
FIFO(First-In First-Out,先进先出)算法:操作系统维护一个当前所有在内存中的链表,最新进入的页面放在表尾,最久进入的页面放在表头,发生缺页中断时,淘汰表头的页面并将新调入的页面加到表尾。
缺点:淘汰的目标不确定,很少使用纯粹的FIFO算法。
2.5.4 第二次机会页面置换算法
第二次机会(second chance)算法改进了FIFO算法,维持一个链表,检查最老页面的R位,如果这个页面没有被访问,如果该位为0,即没有被使用,则立刻置换掉;如果是1,则将该位清0,并把该页面放在链表的尾端,修改它的装入时间,然后继续搜索。
2.5.5 时钟页面置换算法
Second chance算法维护一个链表的代价太高,因此通过时钟环来取代链表;发生缺页中断时,如果它的R位是0就淘汰,并且把新页面插入到这个位置,然后把表针前移一个位置;如果是1,则清除R位并把指针前移直到找到了一个R位为0的页面。
2.5.6 最近最少使用页面置换算法
LRU(Least Recently Used,最近最少使用)算法是最优算法一种近似。有两种使用特殊硬件实现LRU的方法:
1) 要求硬件有一个64位计数器C,每次运行一条指令,这个计数器加一,访问一个页框时,这个计数器保存到对应的页表项中,当发生缺页中断时,找到最小的计数的页面进行淘汰。
2) 在一个有n个页框的机器中,系统维持一个初始值为0的矩阵,访问页框k时,将k行全部设置为1,然后把k列设置为0,发生缺页中断时找到二进制数值最小的行将其淘汰。
2.5.7 用软件模拟LRU
由于很少有硬件支持LRU硬件,因此通常使用软件模拟,一种方案被称为NFU(Not Frequently Used,最不常用)算法;
原理:每个页面有一个软件计数器,初值为0,每次时钟中断,将扫描所有页面,将其R位(记住:每次时钟中断都将该位清零)加到此页面的计数器上,发生缺页中断时,淘汰计数器最小的页面。
缺陷:从不忘记任何事情,受之前程序的运行的影响太深。
改进:老化(aging)算法,在R位被加进去之前先将计数器右移一位,然后将R位加到最左端的位而不是最右端。
LRU算法与老化算法的两点区别:
1) 因为LRU算法在每个时钟滴答中只记录了一位,所以无法区分哪个页面在较早的时间被访问以及哪个页面在较晚的时间被访问,而老化算法能记录先后顺序。
2) 老化算法的计数器只有有限位数,这就限制了对以往页面的记录;这是合理的,比如一个时钟滴答周期是20ms,计数器是8位,若该页面已经有160ms没有被访问,那么它可能就不重要。
2.5.8 工作集页面置换算法
请求调页(demand paging):启动一个进程时,CPU取第一条指令就会产生缺页中断,操作系统转入含有第一个指令的页面,之后对其他数据和指令的访问会依次产生缺页中断,一段时间后,大部分页面都在内存中。
工作集(working set):一个进程当前正在使用的页面的集合。
颠簸(thrashing):每执行几条指令就发生一次缺页中断。
工作集模型(working set model):分页系统设法跟踪进程的工作集,以确保在让进程运行之前,它的工作集就已在内存中。
预先调页(prepaging):在让进程运行之前预先装入工作集页面。
计算工作集有两种方法:
1) 在任一时刻t,都存在一个集合,它包含所有最近k次内存访问所访问过的页面,这个集合我w(k, t)就是工作集。
2) 另一种开销小的方法,不是向后找最近k次的内存访问,而是考虑其执行时间,进程的工作集被称为在过去t秒实际运行时间中它所访问过的页面的集合。
“当前实际工作时间”定义:如果一个进程在T时刻运行,在(T+100)ms的时刻使用了40ms CPU时间,对工作集而言,它的时间就是40ms,一个进程从它开始执行到当前实际使用的CPU时间总数被称为当前实际工作时间。
2.5.9 工作集时钟页面置换算法
刚开始,该表是空的,随着更多页面加入,形成一个环,发生缺页中断时,先检查指针指向的页面,如果R为1,则重置R位,将指针指向下一个页面;如果R位为0,如果生存时间大于t且页面干净,则它已不在工作集,把新页面放入这里;如果该页面被修改过,为了避免磁盘操作引起的进程切换,指针继续往前走,对下一个页面进行操作。
2.5.10 页面置换算法小结
最好的两种算法是老化算法和工作集时钟算法,分别给予LRU和工作集,具有良好的调度性能,且可以被有效实现。