笔记内容及图片整理自XJTUSE "操作系统" 课程ppt,仅供学习交流使用,谢谢。
背景
进程必须全部放入物理内存后方可运行,这个规则将程序大小限制为物理内存大小。许多情况下并不需要将整个程序置于内存中,比如程序几乎从不执行但必须具备的处理异常错误条件的代码。即使在需要整个程序的情况下,也可能并非同时需要,因此可以采用分段以执行只有部分处于内存的程序,使得程序不再受物理内存的可用量所限制。
常规存储器具有以下两个特征:
1)一次性------作业在运行前需要一次性全部装入内存
2)驻留性------作业装入内存后便一直驻留在内存中,直至作业结束。
一次性和驻留性使程序中暂时不用的数据占用了大量的内存空间,从而导致需要运行的作业无法装入内存。
局部性原理:在一段时间内,程序执行仅局限于某个部分中;相应地,它所访问的存储空间也局限于某个区域内。局部性表现为以下两个性质:
1)时间局部性------如果程序中的某条指令一旦执行,则不久的将来该指令可能再次被执行;
如果某个存储单元被访问,则不久以后该存储单元可能再次被访问。产生时间局部性的典型原因是在程序中存在着大量的循环操作。
2)空间局部性------一旦程序访问了某个存储单元,则在不久的将来,其附近的存储单元也最有可能被访问。 即程序在一段时间内所访问的地址,可能集中在一定的范围内,其典型原因是程序是顺序执行的。
虚拟内存是一种允许进程部分装入内存就可以执行的技术,它能将用户逻辑内存与物理内存分开,为程序员提供了巨大的虚拟内存。虚拟内存技术需要解决的主要问题是从物理和逻辑上扩充内存容量。下图展示了虚拟内存大于物理内存。
虚拟内存建立在离散分配的存储管理基础上,它能够通过请求页式或请求段式来执行。
请求页式建立在基本分页的基础上,增加了请求调页和页面置换功能后形成的页式虚拟存储系统,需要硬件和软件支持。
虚拟存储器具有以下特征:
1)离散性------在内存分配时采用离散的分配方式,是虚拟存储器的最基本的特征。
2)多次性------一个作业被分成多次调入内存运行,即在作业运行时不需要将其全部装入,只须将当前要运行的那部分程序和数据装入内存即可,是虚拟存储器最重要的特征。
3)对换性------作业运行过程中信息在内存和外存的对换区之间换进换出。
4)虚拟性------从逻辑上扩充内存容量,使用户所看到的内存容量远大于实际内存容量。
请求调页
请求调页即仅在程序执行期间被请求时才从被加载到内存,常常用于虚拟内存系统。请求调页类似于具有交换的分页系统,但请求调页不是将整个进程交换到内存中,而是采用惰性交换器,仅在需要时才将页面交换到内存中,调页程序涉及进程的页面。
基本概念
调页程序不是调入整个进程,而是把那些要使用的页调入内存。为能使进程运行,需要事先将一部分要执行的程序和数据调入内存。总体而言,调页程序避免了读入不使用的页,减少了交换时间和所需物理内存空间。在虚拟存储系统中,外存(硬盘)被分成文件区和对换区两部分,可以分别从这两部分调入页面。对换区(连续分配)的磁盘I/O速度比文件区(离散分配)要高。调页策略分为预调页策略和请求调页策略:
预调页策略------主动的调页策略,即把那些预计很快会被访问的程序或数据所在的页面预先调入内存。预测的准确率不高(约50%),主要用于进程的首次调入,也有的系统将预调页策略用于请求调页。
请求调页策略------当进程在运行中发生缺页时,由系统将缺页调入内存,此策略在调页时须花费较大的系统开销,如需频繁启动磁盘I/O。目前虚拟存储器系统大多采用此策略。
硬件支持
页表机制------在纯分页的页表机制上形成,由于只将应用程序的一部分调入内存,还有一部分仍在磁盘上,故需在页表中再增加若干项,供程序/数据在换进换出时参考。请求分页系统的每个页表项如图所示。
图中各字段说明如下:
状态位P------用于指示该页是否已调入内存,供程序访问时参考。
访问字段A------用于记录本页在一段时间内被访问的次数,或最近已有多长时间未被访问,提供给置换算法选择换出页面时参考。
修改位M------表示该页在调入内存后是否被修改过。由于内存中的每一页都在外存上保留一份副本。因此,若未被修改,则在置换该页时不需将该页写回到外存上以减少系统的开销和启动磁盘的次数;若已被修改,则必须将该页重写到外存上,以保证外存中所保留的始终是最新副本。
外存地址------用于指出该页在外存上的地址,通常是物理块号,供调入该页时使用。
缺页中断机制------请求调页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,请求操作系统将所缺页调入内存。缺页中断与一般中断的主要区别在于:
1)缺页中断在指令执行期间产生和处理中断信号,而一般中断在一条指令执行完后检查和处理中断信号。
2)缺页中断返回到该指令的开始重新执行该指令,而一般中断返回到该指令的下一条指令执行。
3)一条指令在执行期间,可能产生多次缺页中断。
缺页错误处理步骤如下:
1)查找页表来确定此次地址访问是否合法;
2)若不合法,则中止进程; 否则若有效但不在内存,即发生缺页,则需要将其调入内存;
3)所需页在外存,找到该页;
4)找到一个空闲物理块,启动磁盘,将该页读入内存;
5)读磁盘结束后,修改页表以指出该页已在内存中;
6)重新开始执行刚才发生缺页中断的指令,此时它可以访问刚才调入的页。
地址变换机制------建立在调页系统的地址变换机制的基础上,再为实现虚拟存储器而增加了某些功能所形成,如产生和处理缺页中断,从内存中换出一页的功能等等,下图给出了请求调页系统的地址变换过程。
请求调页的性能
请求调页可以显著影响计算机系统的性能。若未出现缺页错误,则有效访问时间就等于内存访问时间;若出现缺页错误,则应该先从磁盘中读入相关页面,再访问所需要的字。
缺页错误导致以下一组动作发生:
1)陷入操作系统
2)保存用户寄存器和进程状态
3)确定中断是否为缺页中断
4)检查页面引用是否合法,并确定页面的磁盘位置
5)从磁盘读入页面到一个空闲物理块:
A)在磁盘等待队列中等待直到该请求被处理
B)等待设备寻道延迟
C)将该块从磁盘传送至内存
6)为了提高CPU利用率,在等待时将CPU分派给其他进程使用
7)磁盘I/O完成,产生中断
8)若执行了步骤6),则保存正在执行进程的现场信息,否则跳过
9)确认中断来自于上述磁盘
10)修改页表以示所缺的页已进入内存
11)等待CPU再次分派给这个进程
12)恢复该进程的现场信息,包括寄存器、进程状态、页表等,再恢复执行中断的指令
缺页错误的处理时间主要由三个步骤产生:
1)处理缺页中断
2)从磁盘读入所需页
3)重新启动被中断的进程
p为缺页错误率,p = F / A,A是总内存访问次数,F是缺页中断的次数。
有效访问时间 =(1-p)* 内存访问时间 + p * 缺页错误时间
例如内存访问时间1msec,缺页错误时间10msec,有效访问时间为1+9p。
写时复制
通过系统调用fork()的进程创建最初可以通过使用类似于页面共享的技术,绕过请求调页的需要。这种技术提供了快速的进程创建,并最小化必须分配给新创建进程的新页面的数量。
写时复制通过允许父进程和子进程共享相同的页面来工作,这些共享界面标记为写时复制,这意味着如果任何一个进程写入共享页面,那么就创建共享页面的副本。
进程1修改页面C之前:
进程1修改页面C之后:
页面置换
基本页面置换
页面置换指找到内存中并没有真正使用的页,操作系统交换出一个进程,以释放它的所有帧并降低多道程度。页面置换的具体流程:
1)找到所需页面的磁盘位置
2)找到一个空闲帧:
A)若有空闲帧,则使用它
B)若没有空闲帧,则使用页面置换算法来选择一个牺牲帧
C)将牺牲帧的内容写到磁盘上,修改对应的页表和帧表
3)将所需页面读入新的空闲帧,修改页表和帧表
4)从发生缺页错误位置,继续用户进程
如果没有空闲帧,那么需要两个页面传输(一个调入一个调出),它实际上加倍了缺页错误处理时间,并相应地增加了有效访问时间。
若有多个进程在内存中,则必须决定要为每个进程分配多少帧,且当需要页面置换时,必须选择要置换的帧。由于磁盘I/O比较昂贵,此时请求调页方法的稍微改进也会为系统性能带来显著提高,因此页面置换算法比较重要。
进程运行过程中,如果发生缺页,而内存中又无空闲块时,那么应该将内存中的某一页换到磁盘的对换区。页面置换算法需要一个最小的缺页率,它通过运行一个内存访问的特殊序列(访问序列),计算这个序列的缺页次数,具体将哪个页面调出需要根据页面置换算法来确定,算法好坏将直接影响系统的性能,不适当的算法可能会导致进程发生抖动。
FIFO页面置换
为每个页面记录调到内存的时间,置换页面时选择最旧的页面。调到内存的时间不需要是确切的,只需要创建FIFO队列新调入页面加到队列尾部而置换队列首个页面便能实现各页面间的相对顺序。
FIFO页面置换易于理解和编程实现,但所置换的页面可能是很久未使用且已不再使用的初始化模块或者被大量使用且已被初始化过的常用变量,这限制了该算法的性能。
最优页面置换
置换页面时选择之后最长时间不被使用的页。该算法具有最低的缺页错误率,但由于需要引用串的未来知识,易于理解但是难以编程实现。
LRU页面置换
LRU即最近最少使用算法,置换页面时将最近的过去作为不久的将来的一种近似,选择最长时间没有使用的页。该算法将每个页面与它上次使用的时间关联起来,是一种时间上向后看而非向前看的最有页面置换算法,性能不错但是难以实现,它需要重要的硬件辅助,还需要由计数器或堆栈确定由上次使用时间定义的帧的顺序。
近似LRU页面置换
很少有计算机系统能提供足够的硬件以支持真正的LRU页面置换算法,因而使用近似LRU页面置换算法。引入两个变量,修改位(当页面内发生任何修改时,硬件设置修改位,表示该页面已经被修改过)和引用位(当引用一个页面时,无论读写,硬件设置引用位)。
额外引用位算法------通过定期记录引用位以获得额外的排序信息。每个页都与一个位相关联,初始值设为0(访问页设为1),优先置换位为0的页。
第二次机会算法------建立在FIFO算法上,选择页面时需要检查引用位,若引用位值为0则直接置换,若引用位值为1则给予其第二次机会,继续选择下一个FIFO页面。
增强型第二次机会算法------通过将引用位A和修改位M作为有序对改进第二次机会算法,不仅考虑页面使用情况,而且考虑置换代价。该算法将页面区分为四类:第0类(无访问,无修改)、第1类(无访问,有修改)、第2类(有访问,无修改)、第3类(有访问,有修改)。置换页面时先遍历选择第0类置换,若未找到则选择第1类置换,若仍未找到则指针回到开始位置,将所有的引用位重置为0后重复上面两步。
基于计数的页面置换
用一个计数器记录对每一个页的访问次数,有最不经常使用算法(置换计数器值最小的页,即访问次数最少的页)和最经常使用算法(由于最小计数的页也许刚刚被换入和使用,所以置换计数器值最大的页)两个方案。
帧分配
帧的最小数
分配给每个进程的帧数量减少会导致缺页错误率增加,从而影响进程性能。每个进程的最小帧数由体系结构决定,最大帧数由可用物理内存数量决定。每个进程所需要的最少的页数取决于:1)保证进程正常运行所需的最小物理块数2)系统为某进程分配的物理块数少于此值时,进程将无法正常运行(频繁发生缺页)3)指令的格式、功能和寻址方式。
分配算法
主要的分配策略包括平均分配、比例分配和优先分配。
平均分配------为n个进程分配m个帧 --> 为每个进程分配平均值m/n左右的帧数
比例分配------根据每个进程的大小来分配可用内存。
优先分配------根据优先级而不是进程大小来使用比例分配策略。
全局分配与局部分配
为各个进程分配帧的重要因素是页面置换,由于多个进程竞争帧,页面置换算法被分为全局置换(一个进程从所有帧的集合中选择一个置换帧,而不管该帧是否已分配给其他进程)和局部置换(每个进程只从它自己被分配的帧中进行选择)两类。局部置换时分配给每个进程的帧数不变,全局置换时一个进程的帧数可能会增加。相对而言,全局置换带来较高的系统吞吐率,但是无法控制缺页错误率。
系统抖动
如果低优先级进程分配到的帧数小于计算机体系结构所要求的最小数量,那么必须暂停进程执行,然后将其所有剩余页面置换出去,以便释放所有分配的帧。这个规定引入了中级CPU调度的换进换出层。
如果进程没有这些必需的帧,那么很快会出现缺页,此时需置换某个页,然而其所有页都在使用,置换出去的页立刻又需要置换进来,因此会不断产生缺页。这种频繁的调页行为称为颠簸,也即系统抖动。系统抖动的具体定义是刚被换出的页很快又被访问,需要重新调入,导致系统频繁地交换页面,花在换页上的时间要大于进程执行时间,以致大部分CPU时间花费在完成页面置换的工作上。
抖动及其导致的交换对性能的负面影响很大,因而在可能的情况下提供足够的物流内存空间以避免抖动和交换。
系统抖动的原因
若CPU利用率低下,则需要向操作系统引入新的进程来增加多道程度。当进程的个数达到一定数量时,内存紧张。采用全局置换算法会置换任何页面,而不管这些页属于哪个进程。而由于其他进程也需要这些页,因此会出现缺页,从而再从其他进程中置换页。进程在磁盘等待队列等待换页,就绪队列变空,CPU利用率下降。
下图是系统抖动现象。其中CPU利用率按照多道程序来绘制,CPU利用率随着多道程度增加而增加,虽然增加速度较慢,但是会持续增加直至达到最大值。若多道程度进一步增加,则系统抖动开始且CPU利用率急剧下降。此时为了提高CPU利用率并停止抖动,必须降低多道程度。
通过局部置换算法或优先权置换算法可以限制系统抖动,但也会增加进程的有效访问时间,因此为防止颠簸,应该给进程提供足够多的帧。局部模型是一个进程目前活跃页面的集合,
取决于程序的结构和它的数据结构,进程从一个局部移到另一个局部,且局部可能重叠。
工作集模型
工作集模型基于局部性假设。它的基本思想根据程序的局部性原理:
1)进程在一段时间内总是集中访问活跃页面;
2)若分配给一个进程的物理块数太少,使该进程的活跃页面不能全部装入内存,则进程在运行过程中将频繁发生缺页;
3)若能够为进程提供与活跃页面数相等的物理块数,则可减少缺页中断次数。
工作集窗口(Δ)是指对于给定的访问序列选取定长的区间,落在工作集窗口中的页面集合称为工作集。正确选择工作集窗口(Δ)的大小,对存储器的有效利用和系统吞吐量的提高都将产生重要的影响。若工作集窗口太小则不能包含整个局部;若工作集窗口太大则可能包含多个局部;若工作集窗口无限大则工作集合就是进程执行所需的所有页的集合。
工作集模型如下所示:
缺页错误频率
工作集理论可用于预调页,用于防止颠簸,但不够灵活,控制缺页错误频率是一种更加直接的防止颠簸的方法,这是由于颠簸具有较高的缺页率,所以通过控制缺页频率可以有效防止颠簸的发生。控制缺页率需要适当,若缺页率太低则回收一些进程的页框,若缺页率太高则分给进程一些页框。
其他注意事项
预调页面
纯请求调页的特性是当进程启动时,容易发生大量缺页错误。该特性源于试图将最初局部调到内存。例如当换出进程重新启动时,它的所有页面都在磁盘上,且每个页面都会通过缺页错误而调进内存。预调页面试图阻止这种大量的最初调页,它的策略是同时调进所需的所有页面,一些操作系统处理小文件一般采用预调页面。
假设预调s个页面,且这s个页面的a部分被实际使用了(0 <= a <= 1)。关键在于节省的s * a个缺页错误的成本与预调其他s *(1-a)个不必要页面的成本的大小关系。若a接近0则预调页面失败,若a接近1则预调页面成功。
页面大小
选择页面大小的一个关注点是页表大小。对于给定的虚拟内存空间,减小页面大小增加了页面数量,从而增加了页表大小。页面大,则内碎片大;页面小,则页表占用的空间大。
另一个关注点是磁盘I/O时间。磁盘I/O时间包括寻道、延迟和传输时间,传输时间与传输数量(页面大小)成正比,寻道时间和旋转延迟时间远远超过传输时间,因此要最小化I/O时间期望较大的页面大小。
事实上,采用较小页面大小通常减少I/O及内存的占用,因为局部性被改进,较小页面允许每个页面更精确地匹配程序的局部性。
然而,操作系统发展的大趋势是页面尺寸越来越大,这是由于CPU速度和内存容量的增长幅度远远超过了磁盘速度的增长幅度。
TLB范围
正如内存管理策略一章所学,TLB命中率指通过TLB而非页表所进行的虚拟地址转换的百分比,增加命中率的方法是增加TLB的条目数,当然用于构造TLB的关联内存既昂贵又耗能。TLB范围指通过TLB可以访问的内存量,即TLB的条目数与页面大小的乘积,因此增加TLB的条目数、增加页面大小或提供多个页面大小都能增加TLB范围。理想情况下,进程的工作集都应当处于TLB,否则进程的时间成本将大量增加。