存储管理
主存储器,辅助存储器,高速缓冲存储器,寄存器
寄存器:是处理器的组成部分,用来存放处理器的工作信息
高速缓冲存储器:减少对主存储器的访问时间,加快程序的执行速度
主存储器,辅存储器:存放的大量的程序和数据
主存储器的空间分两部分:系统区和用户区
系统区:用来存放操作系统和硬件的接口信息,操作系统的管理信息,程序,标准子程序
用户区:用来存放用户的程序和数据
主存空间的分配和回收
当主存中某个作业撤离或主动归还主存空间时,就回收它所占有的全部或部分的主存空间。
虚拟内存:
虚拟内存的基本思想是:每个进程拥有自己的地址空间,这个空间被分割成多个快,每一块称作一页或者页面,每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。
分页:
由程序产生的这些地址称为虚拟地址,他们构成了一个虚拟地址空间。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字,而在使用虚拟内存的情况下,虚拟地址不是被直接送到内存总线上,而是被送到内存管理单元(Memory Management Unit,MMU),MMU把虚拟地址映射为物理内存地址。
虚拟地址空间按照固定大小划分成称为页面的若干个单元,在物理内存中对应的单元称为页框,页面和页框的大小通常是一样的。
MMU映射机制进行映射
输入的16位虚拟地址被分为4位的页号和12位的偏移量。4位的页号可以表示16个页面,12位的偏移量可以为一页内的全部4096个字节编址
页号作为页表的索引,以得出对应于该虚拟页面的页框号。
页表
虚拟地址到物理地址的映射可以概括如下:虚拟地址被分成虚拟页号(高位部分)和偏移量(低位部分)两部分。
虚拟页号可用作页表的索引,已找到该虚拟页对应的页表项,由页表项可以找到页框号,然后把页框号拼接到偏移量的高端位,以替换掉虚拟页号,形成送往内存的物理地址。
页表的目的是把虚拟页面映射成页框,从数学的角度说,页表是一个函数,它的参数是虚拟页号,结果是物理页框号,通过这个函数可以把虚拟地址中的虚拟页面域替换成页框域,从而形成物理地址。
页表项的结构
最重要的域是页框号,毕竟页映射的目的是找到这个值,其次是“在/不在”位,这一位是1时表示该表项是有效的,可以使用,如果是0,则表示该表项对应的虚拟页面现在不在内存中,访问该页面会引起一个缺页中断。
为了记录页面的使用情况,引入了“修改”(modified)位和“访问位”(referenced)。如果一个页面已经被修改过(即它是“脏”的,)则必须把它写回磁盘。如果一个页面没有被修改过(即它是“干净”的,)简单的把它丢弃就可以了,因为磁盘上的副本仍然有效。
计算机设置了一个小型的硬件设备,将虚拟地址直接映射到物理地址,而不必再访问页表,这种设备称为“转换检测缓冲区(Translation Lookaside Buffer,TLB)”,有时又称为相连存储器。
MMU中的表项,包括虚拟页号,页面的修改位,保护码(读/写/执行权限)和该页所对应的物理页框。
TLB的工作:
将一个虚拟地址放入MMU中进行转换时,硬件首先通过将该虚拟页号与TLB中的所有表项同时进行匹配,判断虚拟页面是否在其中,如果发现了一个有效的匹配并且要进行的访问操作并不违反保护位,则将页框号直接从TLB中取出而不必再访问页表,如果虚拟页面号确实在TLB中,但指令试图在一个只读页面上进行写操作,则会产生一个保护错误,就像对页表进行非法访问一样。
当虚拟页号不在TLB中,即如果MMU检测到没有有效的匹配项时,就会进行正常的页表查询,接着从TLB中淘汰一个表项,然后用新找到的页表项代替它,如果这一页面很快再被访问,,第二次访问TLB时自然将会命中而不是不命中,当一个表项被清除出TLB时,将修改位复制到内存中的页表项,而除了访问位,其他的值不变,当页表项中从页表装入到TLB中时,所有的值都来自内存。
无论是硬件还是软件来处理TLB失效,常见方法都是找到页表并执行索引操作已定位将要访问的页面。
软失效:当一个页面访问在内存中而不在TLB中时
硬失效;页面本身不在内存中(当然也不在TLB中)
多级页表:
虚拟地址被划分成PT1域,PT2域,Offset(偏移量)域
当一个虚拟地址被送到MMU时,MMU首先提取PT1域并把该值作为访问顶级页表的索引。由索引顶级页表得到的表项中含有二级页表的地址或页框号。现在把PT2域作为访问选定的二级页表的索引,以便找到该虚拟页面的对应页框号。从二级页表中得到的页框号与偏移量结合形成物理地址,该地址被放到总线上并送到内存中。
倒排表(inverted page table)
在实际内存中每一个页框有一个表项,而不是每一个虚拟页面有一个表项。表项记录哪一个(进程,虚拟页面)对位于该页框。
倒排表的不足:当进程n访问虚拟页面P时,硬件不能再通过把P当作指向页表的一个索引来查找物理页框,取而代之的是,他必须对每一个内存的访问操作都要执行一次,而不仅仅是发生在缺页中断时执行。
使用TLB可以解决这个问题。如果TLB能够记录所有频繁使用的页面,地址转换就可能变得像通常快表一样,但是,当TLB失效时,需要用软件搜索整个倒排页表。
页面置换算法:
当发生缺页中断时,操作系统必须在内存中选择一个页面将其换出内存,以便为即将调入的页面腾出空间,如果要换出的页面在内存驻留期间已经被修改过,那么它在磁盘上的副本已经是最新的,不需要回写,直接用调入的页面覆盖掉被淘汰的页面就可以了。
1:最优页面置换算法
2:最近未使用页面置换算法
系统为每一个页面设置了两个状态位,当页面被访问(读或者写)时设置R位,当页面(即修改页面)被写入时设置M位,这些位都包含在页表项中。
用R位或者M位来构造一个简单的页面置换算法:当启动一个进程时,他的所有页面的两个位都由操作系统设置成0,R位被定期的(比如在每次时钟中断时)清零,以区别最近没有被访问的页面和被访问的页面。
当发生缺页中断时,操作系统检查所有的页面并根据它们当前的R位和M位的值,把它们分为4类:
0:没有被访问,没有被修改
1:没有被访问,已被修改
2:已被访问,没有被修改
3:已被访问,已被修改
NRU(Not Recently Used,最近未使用)算法随机地从类编号最小的非空类中挑选一个页面淘汰。即,在最近的一个时钟滴答中淘汰一个没有被访问的已修改页面要比淘汰一个被频繁使用的“干净”页面好。
3:先进先出页面置换算法
FIFO(First-In First-Out,先进先出)由操作系统维护一个所有当前在内存中的页面的链表。最新进入的页面放在表尾,最久进入的页面放在表头,当发生缺页中断时,淘汰表头的页面并把新调入的页面加在表尾。
第二次机会(second chance)第二次机会算法就是寻找一个最近的时钟间隔以来没有被访问过得页面。如果所有的页面都被访问过了,该算法就被简化成纯粹的FIFO算法。
4:时钟页面置换算法
把所有的页面都保持在一个类似钟面的环形链表中,一个表针指向最老的页面,当发生缺页中断时,算法首先检查表针指向的页面,并把新的页面插向这个位置,然后把表针前移一个位置,如果R为是1就清除R位并把表针前移一个位置,重复这个过程,直到找到一个R位为0 的页面为止。
5:最近最少使用页面置换算法
LRU(Least Recently Used最近最少使用算法)
特殊硬件:要求硬件有一个64位的计数器C,它在每条指令执行完之后自动加1,每个页表项必须有一个足够容纳这个计数器值的域,在每次访问内存后,将当前的C值保存到被访问页面的页表项中,一旦发生缺页中断,操作系统就检查所有页表项中计数器的值,找到值最小的一个页面,这个页面就是最近最少使用的页面。
6:NFU(Not Frequently Used,最不常用)算法
该算法将每个页面与一个软件计数器相关联,计数器的初值为0,每次时钟中断时,由操作系统扫面内存中的所有页面,将每个页面的R位(它的值是0或者1)加到它的计数器上,这个计数器大体上跟踪了各个页面被访问的频繁程度,发生缺页中断时,则置换计数器值最小的页面。
6:老化算法(aging)
对NFU做一个小小的修改就能好的模拟LRU,其修改分为两个部分:在R位被加进之前先将计数器右移一位,其次,将R位加在计数器最左端的位而不是最右端的位。
7:工作集页面置换算法
请求调页(demand paging),因为页面是在需要时被调入的,而不是预先装入
在单纯的分页系统里,刚启动进程时,在内存中并没有页面,在CPU试图取第一条指令时就会产生一次缺页中断,使操作系统装入含有第一条指令的页面,其他由访问全局数据和堆栈引起的缺页中断会紧接着发生,一段时间后,进程需要的大部分页面都在内存中了,进程在较少的缺页中断下运行。
工作集(working set)一个进程当前正在使用的页面的集合。
颠簸(thrashing)若每执行几条指令程序就发生一次缺页中断,那么就称这个程序发生了颠簸。
在多道程序设计系统中,经常会把进程转移到磁盘上(即从内存中移走所有的页面),这样可以让其他的进程有机会占有CPU。
工作集模型(working set model):不少分页系统都会设法跟踪进程的工作集,以确保再让进程运行以前,它的工作集就已经在内存中了,其目的在于大大减少了缺页中断率,在让进程运行前预先装入其工作集页面也称为预先调页(paging).预先调页就是在程序运行之前预先装入推测出的工作集的页面。
为了实现工作集模型,操作系统必须跟踪哪些页面在工作集中,通过这些信息可以直接推导出一个合理的页面置换算法:当发生缺页中断时,淘汰一个不在工作集中的页面。工作集就是最近K次内存访问所使用过的页面的集合。为了实现工作集算法,必须预先选定K的值,一旦选定某个值,每次内存访问之后,最近K次内存访问所使用过的页面的集合
设想有一个长度为K的移位寄存器,每进行一次内存访问就把移位寄存器左移一位,然后在最右端插入刚才所访问过的页面号,移位寄存器中的K个页面号的集合就是工作集。理论上,当缺页中断发生时,只要读出移位寄存器中的内容并排序,然后删除重复的页面,结果就是工作集。
当前实际运行时间:一个进程从它开始执行到当前所实际使用的CPU时间总数。
近似方法:不是向后找最近K次的内存访问,而是考虑其执行时间。通过这个近似的方法,进程的工作集可以被称为在过去的T秒实际运行时间中它所访问过的页面的集合。
8:基于工作集的页面置换算法:基本思路就是找出一个不在工作集中的页面并淘汰它。
注:只有那些在内存中的页面才可以作为候选者被淘汰,所以该算法忽略了那些不在内存中的页面。每个表项至少包含两条信息:上次使用该页面的近似时间和R(访问)位,空白的矩阵表示该算法不需要其他的域,如页框号,保护位,M(修改)位。
工作如下:假定使用硬件来设置R位和M位,同样,假定在每个时钟滴答中,有一个定期的时钟中断会用软件方法来清除R位,每当缺页中断发生时,扫面页表以找出一个合适的页面淘汰之。
1:在处理每个表项时,都需要检查R位,如果它是1,就把当前实际时间写进页表项的“上次使用时间域”,以表示缺页中断发生时该页面正在使用。既然该页面在当前时钟滴答中已经被访问过,那么很明显它应该出现在工作集中,并且不应该被删除。
2:如果R是0,那么表示在当前时钟滴答中,该页面还没有被访问过,则它就可以作为候选者被置换,为了知道它是否应该被置换,需要计算它的生存时间(即当前实际运行时间减去上次使用时间),然后与T做比较,如果它的生存时间大于T,那么这个页面就不在工作集中,而用新的页面置换它,扫面会继续进行以更新剩余的表项。
3:如果R是0,同时生存时间小于或等于T,则该页面仍然在工作集中,这样就把该页面临时保留下来,但是要记录生存时间最长(“上次使用时间”的最小值)的页面。如果扫描整个页表却没有找到适合被淘汰的页面,也就意味着所有的页面都在工作集中。这种情况下。如果找到了一个或者多个R=0 的页面,就淘汰生存时间最长的页面。最坏情况下,在当前的时间滴答中,所有的页面都被访问过了(也就R=1),因此就随机选择一个页面淘汰,如果有的话,最好选一个干净页面。
9:工作集时钟页面置换算法(WSClock,工作集时钟)
与时钟算法一样,所需的数据结构是一个以页框为元素的循环表,最初,该表是空的,当装入第一个页面后,把它加到该表中,随着更多的页面加入,它们形成一个环,每个表项来自基本工作集算法的上次使用时间,以及R位(以标明)和M位(未标明)
与时钟算法一致,每次缺页中断时,首先检查指针指向的页面,如果R位被置为1,该页面在当前时钟滴答中就被使用过,那么该页面就不适合淘汰,然后把该页面的R位置为0,指针指向下一个页面,并重复该算法。
如果页面的生存时间大于T并且该页面是干净的,它就不在工作集中,并且在磁盘上有一个有效的副本。申请此页框,并且把新页面放在其中。另一方面,如果此页面被修改过,就不能立即申请页框。因为这个页面在磁盘上没有有效的副本,为了避免由于调度写磁盘操作引起的进程切换,指针继续向前走,算法继续对下一个页面进行操作,毕竟,有可能存在一个旧的且干净的页面可以立即使用。
所有的页面都有可能因为磁盘I/O在某个时钟周期被调度,为了降低磁盘阻塞,需要设置一个限制,即最大只允许写回n个页面,一旦达到该限制,就不允许调度新的写操作。
1)至少调度了一次写操作
指针不停的移动,寻找一个干净的页面,既然已经调度了一个或者多个写操作,最终会有某个写操作完成,它的页面会被标记为干净,置换遇到的第一个干净页面,这个页面不一定是第一个被调度写操作的页面,因为硬盘驱动程序为了优化性能可能已经把写操作重排序了。
2)没有调度过写操作
所有的页面都在工作集中,否则将至少调度了一个写操作,由于缺乏额外的信息,一个简单的方法就是随便置换一个干净的页面来使用,扫描中需要记录干净页面的位置,如果不存在干净的页面,就选定当前页面并把它写回磁盘。
怎么样在相互竞争的可运行进程之间分配内存?
局部算法可以有效的为每个进程分配固定的内存片段,全局算法可以运行进程之间动态的分配页框。因此分配给各个进程的页框数是随时间变化的。
1:全局算法在通常情况下工作的比局部算法好,当工作集的大小随进程运行时间发生变化时这种现象更加明显,若使用局部算法,即使有大量的空闲页框存在,工作集的增长也会导致颠簸,如果工作集缩小了,局部算法又会浪费内存,在使用全局算法时,系统必须不停滴地确定应该给每个进程分配多少页框,一种方法是监测工作集的大小,工作集大小由“老化”位指出,但这个方法并不能防止颠簸,因为工作集的大小可能几微秒内就会发生改变,而老化位却要经历一定的时钟数才能发生变化。
2:另一种方法是:使用一个进程分配页框的算法。其中一种方法是定期确定进程运行的数目并为它们分配相等的份额。
可以采用按照进程大小的比例来为它们分配相应数目的页面的方法来取代上一种方法。比较明智的一个可行的做法是对每个进程都规定一个最小的页框数,这样不论多小的进程都可以运行。
如果使用全局算法,根据进程的大小按比例为其分配页面是有可能的,但是该分配必须在程序运行时动态更新,管理内存动态分配的一种方法是使用PFF(Page Fault Frequency,缺页中断率)算法,它们提出了何时增加或减少分配给一个进程的页面,但却完全没有说明在发生缺页中断时应该替换掉哪一个页面,它仅仅控制分配集的大小
负载控制
减少竞争内存的进程数的一个好方法是将一部分进程交换到磁盘,并释放他们所占有的所有页面。
一个进程被交换到磁盘,而它的页框可以被其他处于颠簸的状态的进程分享,如果颠簸停止,系统就可以这样运行一段时间,如果颠簸没有结束,需要继续将其他进程交换出去,知道颠簸结束,即使是使用分页,交换也是需要的,只是现在交换是用来减少内存潜在的需求,而不是收回它的页面。
将进程交换出去的以减轻内存需求的压力是借用了两级调度的思想,在此过程中一些进程被放到磁盘,此时用一个短期的调度程序来调度剩余的进程,很明显,这两种思路可以被组合起来,将恰好足够的进程交换出去以获取可接受的缺页中断率,一些进程被周期性的从磁盘调入,而其他一些则被周期性的交换到磁盘。
页面大小
内部碎片:平均情况下,最后一个页面中有一半是空的,多余的空间就被浪费掉了,这种浪费叫做内部碎片。在内存中有n个段,页面大小位p字节时,会有np/2字节被内部碎片浪费。
假设进程平均大小是s个字节,页面大小是p个字节,每个页表项需要e个字节,那么每个进程需要的页数大约是s/p,占用了se/p个字节的页表空间,内部碎片在最后一页浪费的内存是p/2,由页表和内部碎片损失造成的全部开销是以下两项之和:
开销=se/p+p/2
在页面比较小的时候,第一项(页表大小)大,在页面比较大的时候,第二项(内部碎片)大,最优值一定在页面大小处于中间的某个值时取得,通过对p一次求导并令右边等于0,则:
-se/p^2+1/2=0
P=
共享页面
只读的页面可以共享,数据页面则不能共享
每个进程在它的进程表中都有两个指针:一个指向I空间页表,一个指向D空间页表。当调度程序选择一个程序运行时,它使用这些指针来定位合适的页表,并使用它们来设立MMU。
写时复制:只要这两个进程都仅仅是读数据,而不是做修改,这种情况就可以保持下去,但只要有一个进程更新了一点数据,就会触发只读保护,并引发操作系统陷阱,然后生成一个该页的副本,这样每个进程都有自己的专用副本,两个复制都是可以读写的,随后对任何一个副本的写操作都不会再引发陷阱,这种策略意味着那些从来不会执行写操作的页面是不需要复制的,只有实际修改的数据页面需要复制
共享库:
未定义外部函数:任何在目标文件中被调用了但是没有被定义的函数。
链接器会在库中寻找这些未定义外部函数,如果找到了,则将它们加载到可执行二进制文件中,任何被这些未定义外部函数调用了但是不存在的函数也会成为未定义的外部函数。
当一个程序和共享库(与静态库有些区别)链接时,链接器没有加载被调用的函数,而是加载了一小段能偶够在运行时绑定被调用函数的存根例程(stub routine)依赖于系统和配置信息,共享库或者程序一起被装载,或者在其所包含函数第一次被调用时被装载,如果其他程序已经装载了某个共享库,就没有必要再装载它了。当一个共享库被装载和使用时,整个库并不是一次性的读入内存,而是根据需要,以页面为单位装载的,因此没有被调用的函数是不会被装载到内存的。
优点:如果共享库中的一个函数因为修正一个bug被更新了,那么并不需要重新编译调用了这个函数的程序。旧的二进制文件依然可以正常工作。
注:库被装载到的物理地址与这个库是否为共享库是没有任何关系的,因为所有的页面都被MMU硬件从虚拟地址映射到了物理地址。
解决它的一个办法是:写时复制,并为每一个共享这个库的进程创建新页面,在创建新页面的过程中进行重定位。
一个更好的解决办法:在编译共享库时,用一个特殊的编译选项告知编译器,不要产生使用绝对地址的指令,相反,只能产生使用相对地址的指令。
位置无关代码:只使用相对偏移量的代码
内存映射文件(memory-mapped file)
思想:进程可以通过发起一个系统调用,将一个文件映射到其虚拟地址空间的一部分,在多数实现中,在映射共享的页面时不会实际读入页面的内容,而是在访问页面时才会被每次一页地读入,磁盘文件则被当作后备存储,当进程退出或显式的解除文件映射时,所有被改动的页面会被写回到文件中。
如果两个或两个以上的进程同时映射了同一个文件,它们就可以通过共享内存来通信,一个进程在共享内存上完成了写操作,此刻当另一个进程在映射到这个文件的虚拟地址空间上执行读操作时,它就可以立刻看到上一个进程写操作的结果。
清除策略
如果发生缺页中断时系统有大量的空闲页框,此时分页系统工作在最佳状态。如果每个页框都被占用,而且被修改的话,再换入一个新页面时,旧页面应首先被写回磁盘。为保证有足够的空闲页框,很多分页系统有一个称为分页守护进程(paging daemon)的后台进程,它在大多数时候睡眠,但定期被唤醒已检查内存的状态,如果空闲页框过少,分页守护进程通过预订的页面置换算法选择页面换出内存,如果这些页面装入内存被修改过,则将它们写回磁盘。
在任何情况下,页面中原先的内容都被记录下来,当需要使用一个已被淘汰的页面时,如果该页框还没有被覆盖,将其从空闲页框缓冲池中移出即可恢复该页面,保存一定数目的页框供给比使用所有内存并在需要时搜索一个页框有更好的性能,分页守护进程至少保证了所有的空闲页框是“干净”的,所以空闲页框在分配时不必着急写回磁盘
一个实现清除策略的方法就是使用一个双指针时钟。前指针油分页守护进程控制,当它指向一个脏页面时,就把该页面写回磁盘,前指针向前移动,当它指向一个干净的页面时,仅仅指针向前移动,后指针用于页面置换,就像在标准时钟算法中一样。现在,由于分页守护进程的工作,后指针命中干净页面的概率会增加。
虚拟内存接口
程序员可以对内存映射进行控制,是为了允许两个或者多个进程共享同一部分内存,如果程序员可以对内存区域进行命名,那么就可以实现共享内存,通过让一个进程把一片内存区域的名称通知另一个进程,而使得第二个进程可以把这片区域映射到它的虚拟地址空间去。通过两个进程(或者更多)共享同一部分页面,高带宽的共享就成为可能——一个进程往共享内存中写内容而另一个从中读出内容。
页面共享也可以用来实现高性能的消息传递系统,一般地,传递消息的时候,数据被从一个地址空间复制到另一个地址空间,开销很大,如果进程可以控制他们的页面映射,就可以发来这样一条消息:发送进程清除那些包含信息的页面的映射,而接收进程把它们进来,这里只需要复制页面的名字,而不需要复制所有的数据。
分布式共享内存:高级存储管理技术
该方法允许网络上的多个进程共享一个页面集合,这些页面可能作为当个的线性共享地址空间,当一个进程访问当前还没有映射进来的页面时,就会产生缺页中断。在内核空间或者用户空间中的缺页中断处理程序就会拥有该页面的机器进行定位,并向他发送一条消息,请求它清除该页面的映射,并通过网络发送过来,当页面到达时,就把它映射进来,并重新开始运行引起缺页中断的指令。
与分页有关的工作:
进程创建,进程执行,缺页中断,进程终止
当在分页系统中创建一个新进程时,操作系统要确定程序和数据在初始时有多大,并为它们创建一个页表,操作系统还要在内存中为页表分配空间并对其进行初始化。当进程换出时,页表不需要驻留在内存中,但当进程进行时,它必须在内存中,另外,操作系统要在磁盘交换区中分配空间,以便在一个进程换出时在磁盘上放置此进程的空间,操作系统还要用程序正文和数据对交换区进行初始化,这样,当新进程发生缺页中断时,可以调入需要的页面,某些系统直接从磁盘上的可执行文件对程序正文进行分页,以减少磁盘空间和初始化时间。最后,操作系统必须把有关页表和磁盘交换区的信息存储在进程表中。
当调度一个进程执行时,必须为新进程重置MMU,刷新TLB,以清除以前的进程遗留的痕迹,新进程的页表必须成为当前页表,通常可以通过复制该页表或者把一个指向它的指针放进某个硬件寄存器来完成,有时,在进程初始化时可以把进程的部分或者全部页面装入内存中以减少缺页中断的发生。
当缺页中断发生时,操作系统必须通过读硬件寄存器来确定是哪个虚拟地址造成了缺页中断,通过该信息,它要计算需要哪些页面,并在磁盘上对该页面进行定位,它必须找到合适的页框来存放新的页面,必要时还要置换老的页面,然后把所需的页面读入页框,最后,还要备份程序计数器,使程序计数器指向引起缺页中断的指令,并重新执行该指令。
当进程退出的时候,操作系统必须释放进程的页表,页面和页面在硬盘上所占用的空间,如果某些页面是与其他进程共享的,当最后一个使用它们的进程终止的时候,才可以释放内存和磁盘上的页面。
缺页中断处理
1:硬件陷入内核,在堆栈中保留程序计数器,大多数机器将当前指令的各种状态信息保存在特殊的CPU寄存器中。
2:启动一个汇编代码历程保存通用寄存器和其他易失的信息,以免被操作系统破坏,这个历程将操作系统作为一个函数来调用
3:当操作系统发现一个缺页中断时,尝试发现需要哪个虚拟页面,通常一个硬件寄存器包含了这一信息,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析这条指令,看看他在缺页中断时做了什么。
4:一旦知道了发生了缺页中断的虚拟地址,操作系统检查这个地址是否有效,并检查存取与保护是否一致,如果不一致,向进程发出一个信号或杀掉该进程。如果地址有效且没有保护错误发生,系统则检查是否有空闲页框,如果没有空闲页框,执行页面置换算法寻找一个页面来淘汰。
5:如果选择的页框“脏了”,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其它进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。
6:一旦页框“干净”后(无论是立刻还是在写回磁盘后),操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入,该页面被装入后,产生的缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则可以选择另一个用户进程运行。
7:当磁盘中断发生时,表明该页已经被装入,页表已经更新可以放映它的位置,页框也被标记为正常状态。
8:恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。
9:调度引发缺页中断的进程,操作系统返回调用它的汇编语言例程。
10:该例程恢复寄存器和其他状态信息,返回到用户空间继续执行,就好像缺页中断没有发生一样。
锁定内存中的页面
设想一个进程刚刚通过系统调用从文件或者其他设备中读取数据到其他地址空间中的缓冲区,在等待I/O完成时,该进程被挂起,另一个进程被允许运行,而这个进程产生一个缺页中断。
如果一个I/O设备正处在对该页面进行DMA传输的过程中,将这个页面移出将会导致部分数据写入他们所属的缓冲区中,而部分数据被写入到重新装入的页面中,一种解决的办法是锁住正在做I/O操作的内存中页面以保证它不会被移出内存。
盯住(pinning)锁住一个页面通常称为在内存中盯住页面。
另一种方法是在内核缓冲区中完成所有的I/O操作,然后再将数据复制到用户页面。
在磁盘上分配页面空间的最简单的算法是在磁盘上设置特殊的交换分区,甚至从文件系统划分出一块独立的磁盘。
分段和分页结合
MULTICS中一个地址由两部分组成:段和段内地址。段内地址又进一步分为页号和页内的字。
1:根据段号找到段描述符
2:检查该段的页表是否在内存中,如果在,则找到它的位置,如果不在,则产生一个错误,如果访问违反了段的保护要求就发出一个越界错误
3:检查所请求虚拟页面的页表项,如果该页面不在内存中则产生一个缺页中断,如果在内存就从页表项中取出这个页面在内存中的起始地址。
4:把偏移量加到页面的起始地址上,得到要访问的字在内存中的地址
5:最后进行读或者写操作
分段和分页结合(Pentium)
两张表:LDT(Local Descriptor Table,局部描述符表),GDT(Global Descriptor Table,全剧描述符表),每个程序都有自己的LDT,共享GDT
LDT描述局部于每个程序的段,包括其代码,数据,堆栈;GDT描述系统段,包括操作系统本身。
CS寄存器保存代码段的选择子(selector),DS保存数据段的选择子。
选择子中的一位指出这个段是局部的还是全局的,其他的13位是LDT或者GDT的表项编号。
在选择子被装入段子存器时,对应的描述符被从LDT和GDT中取出装入微程序寄存器,以便快速的访问。