《现代操作系统》第三章:存储管理

操作系统中管理分层存储器体系的部分称为存储器管理(memory manager)

3.1 无存储器抽象

直接使用物理地址;
并行问题;
重定位问题(因为使用的是绝对物理地址);
装载器需要一定的方法来辨别地址和常数(地址值需要重定位,而常数不需要重定位)。

3.2 一种存储抽象:地址空间

要保证多个应用程序同时处于内存中并且不互相影响,需要解决的问题:保护和重定位。
    保护:给内存块标记保护键(IBM 360)
地址空间是一个进程可用于寻址内存的一套地址集合,每个进程都有一个自己的地址空间。
    使得一个程序中的地址28所对应的物理地址与另一个程序中的地址28对应的物理地址不同。
基址寄存器和界限寄存器
    简单的动态重定位
    前者是程序的起始物理地址,后者是程序的长度,CPU硬件在把地址发送到内存总线前,自动把基址值加到进程发出的地址上。
    缺点:每次访问内存都需要进行加法和比较运算,导致速度相对慢一点。
Intel 8088提供了多个基址寄存器,使程序的代码和数据可以被独立的重定位。但是没有提供引用地址越界的预防机制。
把所有进程都一直保存在内存中需要巨大的内存,如果内存不够,就做不到。
    处理内存超载的通用方法:
        1)交换(swapping)技术:进程运行一段时间,然后再存回磁盘中,周期性的被唤醒与睡眠,不运行时则不占内存。
        2)虚拟内存(virtual memory):使程序只有一部分被调入内存的情况下运行。
交换技术:
    某个进程本次换入内存与上次换入内存时的位置可能会发生变化,则在换入的时候会通过软件或者程序运行期间(多数是这种情况)通过硬件对其地址进行重定位。
    内存紧缩(memory compaction)操作。交换在内存中产生了多个空闲区(hole)也叫空洞,通过把所有的进程尽可能向下移动,有可能将这些小的空闲块合并成一大块。
    进程被创建或换入时需要分配内存的大小问题,为可能增长的数据段预留多一个空闲空间:
        1)分配相邻的空闲区最为可能增长的空
        2)在堆栈之间预留可能增长的空间
动态内存分配时跟踪内存使用情况:
    1)位图:将内存划分成多个单元,通过位图来记录每个单元是否被使用,分配操作比较耗时;分配单元的大小是一个重要的设计因素,内存的大小和分配单元的大小决定了位图的大小。
    2)链表:维护一个记录已分配内存段和空闲内存段的链表。链表中的每一个结点都包含以下域:空闲区(H)或进程(P)的指示标志、起始地址、长度和指向下一结点的指针。
链表的存储管理方法中,为创建或者换入的进程分配内存的方法:
    1)首次适配(first fit)算法:每次都从第一个开始搜索,找第一个合适的;
    2)下次适配(next fit)算法:每次找到合适的空闲区就会记录当时的位置,下次则在这个位置往后继续搜索,性能略低于首次适配法;
    3)最佳适配(best fit)算法:找到合适的最小空闲区,比首次适配算法更浪费内存(会产生大量无用的小空闲区?),需要搜索整个链表;
    4)最差适配(worst fit)算法:总是分配最大的可用空闲区;
    5)快速适配(quick fit)算法:为常用大小的空闲区维护单独的链表,寻找指定大小的空闲块是快速,但是进程终止或被换出时合并操作耗时。
    将进程和空闲区使用不同的链表保存?

3.3 虚拟内存(virtual memory)

虽然软件大小的快速增长,可能内存无法满足单独一个程序的需求,并且硬盘的读取速度会导致换入与换出一个程序很耗时。
虚拟内存的基本思想是:每个程序拥有自己的地址空间,该空间被分割成很多块,每一块称作一页或者页面(page)。
    每一页被映射到物理内存,不是所有的页都必须在内存中才能运行程序。
    程序引用到一部分在物理内存中的地址空间时,由硬件执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。
    从某种角度上讲,虚拟内存是对基址寄存器和界限寄存器的一种综合,使得整个地址空间可以用相对较小的单元映射到物理内存,而不是为正文段和数据段分别进行重定位。
    适合多道程序设计系统,因为许多程序的片段会同时保存在内存中,当一个程序等待它的一部分读入内存时,可以直接把CPU交给另一个进程使用而基本不需要进行进程的换入换出操作。
分页(paging)技术。
    地址可以通过索引、基址寄存器、段寄存器或其他方式产生。
由程序产生的地址称为虚拟地址(virtual address),构成了一个虚拟地址空间(virtual address space)
    1)没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上。
    2)在使用虚拟内存的情况下,虚拟地址是被送到内存管理单元(Memory Management Unit,MMU),MMU把虚拟地址映射为物理内存地址。
虚拟地址空间按照固定大小划分为页面(page)的若干单元,在物理内存中对应的单元称为页框(page frame)。页面大小和页框是一样的。
    RAM和磁盘之间的交换总是以整个页面大小为单元进行。
    在实际的硬件中,用一个“在/不在”位(present/absent bit)记录页面在内存中的实际存在情况。
MMU注意到某个虚拟页面没有被映射到页框时,会使CPU陷入到操作系统,这个陷阱称为缺页中断(page fault)。操作系统再找到一个很少使用的页框将其内容写入到磁盘(如果它不在磁盘上),随后再将需要访问的页面读到刚才回收的页框中,修改映射关系,然后重新启动引起陷阱的指令。
    被收回的虚拟页面要修改为未映射。
    将需要的虚拟页面装入到对应页框中,访问该虚拟页面的地址会被映射到对应的物理内存中。
虚拟地址到物理地址的映射可以概括如下:虚拟地址分为虚拟页号(高位部分)和偏移量(低位部分)两个部分。
    例如32位系统中输入的16位虚拟地址就被分为4位的页号和12位的偏移量,4位的页号可以表示16个页面,12位的偏移可以为一页内的全部4096个字节编址。
    可以用页号作为页表(page table)的索引,以得出对应于该虚拟页面的页框号。  得到页号 ---> 查询页表(如果“在/不在”标志位为1)---> 得到页框号 ---> 加上偏移量 ---> 物理地址。
页表的目的是把虚拟页面映射为页框,类似一个函数,参数是虚拟页号,结果是物理页框号,通过这个函数可以把虚拟地址中的虚拟页域替换成页框域,从而形成物理地址。
每个进程都有自己的页表?
页表项的结构:
    1)高速缓存禁止位:保证硬件是不断从设备中读取数据而非访问一个旧的被高速缓存的副本,对映射到设备寄存器而不是常规内存的页面而言该特性比较重要。具有独立的I/O空间而不使用内存映射I/O的机器不需要该位。
    2)保护位:指出一个页允许什么类型的访问;
    3)修改位:记录页面的使用情况,记录该页是否被修改过,由硬件自动设置修改。如果一个页面被修改过(即它是“脏”的),则必须把它写回磁盘,否则只要简单丢弃即可,有时也被称为脏位(dirty bit);
    4)访问位:用来帮助在发生缺页中断时选择要淘汰的页面;在多页面置换算法中有很重要的作用。
    5)“在/不在”位:记录该表项对应的虚拟页面是否在内存中,是否触发缺页中断;
    6)页框号。
注意页表并不会保存页面的磁盘地址。
虚拟内存的实现,是将虚拟地址空间分解成页,并将每一页映射到物理内存的某个页框或者(暂时)解除映射。
分页需要考虑的问题:
    1)虚拟地址到物理地址的映射必须非常快;
    2)如果虚拟地址空间很大,页表也会很大。
对大而快速的页映射的需求成为了构建计算机的重要约束。
    极端方法1:快速硬件寄存器 ==> 当启动一个进程时,操作系统把保存在内存中的进程页表的副本载入到寄存器中,每次上下文切换都必须装载整个页表,降低了性能。
    极端方法2:整个页表都在内存中
有了分页后,因为要访问页表导致更多次的访问内存,降低一半的性能。
虚拟地址到物理地址的映射必须非常快 ==> 转换检测缓冲区(Translation Lookaside Buffer, TLB,快表?),又称为相关联储存器(associate memory)。
    基于现象:大多数程序总是对少量的页面进行多次的访问,只有很少的页表项会被反复读取。
    用于将虚拟地址直接映射为物理地址的小型硬件设备,通常在MMU中,包含少量的表项,每个表项记录的页面相关信息(除了虚拟页号不是必须的)与页表中域一一对应。
    就是部分页表保存在硬件寄存器中。
    如果TLB中找到指定虚拟页面对应的表项:
        访问操作不违反保护位则返回,否则产生一个保护错误。
    如果TLB中没有找到对应的页表项才陷入到操作系统中:
        进行正常的页表查询,接着中TLB中淘汰一个表项,用找到的页表项代替它。
        当一个表项被清除出TLB时,将修改为复制到内存中的页表项,而除了访问位,其他值不变。
软件TLB管理:
    正常情况下对TLB的管理和TLB的失效处理都完全由MMU硬件来实现,只有在内存中没有找到某个页面时才会陷入到操作系统中。
    现在大多数都TLB失效都是生成一个TLB失效问题交给操作系统解决,TLB表项会被操作系统显示地装载。
    TLB大的话可以减少失效率。
    软失效与硬失效:前者页面在内存中而不再TLB中,而后者页面也不再内存中需要从磁盘中装入。
如果虚拟地址空间很大,页表也会很大 ==> 针对大内存的页表:
    1)多级页表:原因之一是避免把全部页表一直保存在内存中。
        例子:32位的虚拟地址被划分为10位的PT1域、10位的PT2域和12位的Offset(偏移量)域,因为偏移量是12位,所以页面长度是4KB,有2^20个页面。整个4G虚拟地址空间被分成1024个4MB的块。由索引顶级页表得到的表项中含有二级页表的地址或页框号。顶级页表的表项0指向程序正文的页表,表项1指向数据的页表,表项1023指向堆栈的页表。
        存在的必要性?
        页表的级别越多,灵活性越大,但是也越复杂。
    2)倒排页表(inverted page table):就是从页框中查找某个页表是否存在。
        多级页表不适合64位系统,页表耗费的空间非常大。
        在实际内存中每一个页框有一个表项,而不是每一个虚拟页面有一个表项。节省了大量空间,特别是虚拟地址空间比物理内存大得多的时候。
倒排页面相关的知识、处理大虚存的方法。

3.4 页面置换算法

发生缺页中断时如何从内存中选择一个页面换出内存,为新的页面腾出空间。
最优页面置换算法:不可能实现
最近未使用(NRU)页面置换算法:LRU的很粗糙的近似
    用R位(访问)和M位(修改)来构造一个简单的页面置换算法。R位被定期的(比如在每次时钟中断时)清零,以区别最近没有被访问的页面和被访问的页面。所以即使是被频繁访问的页面,也会定期出现缺页中断?
    根据R位和M位的值对所有页面分类:  
        第0类:没有被访问和修改
        第1类:没有被访问,但被修改
        第2类:已被访问,但没被修改
        第3类:已被访问和修改
    NRU随机地从类编号最小的非空类中选择一个页面淘汰之。
先进先出(FIFO)页面置换算法:可能怕抛弃重要页面
    最新的页面放在表尾,最久进入的页面放在表头
    发生缺页中断时,淘汰表头的页面并把新调入的页面放到表尾。
第二次机会页面置换算法:比FIFO有大的改善
    在FIFO算法的基础上改进
    如果最老页面的R位为0,则将其置换掉,如果是1,则将R位清零并将其放到表尾,并且修改它的装入时间使它像刚装入的一样,然后继续搜索。
    就是寻找一个最近的时钟间隔以来没有被访问过的页面,如果所有页面都被访问过了,该算法就简化为纯粹的FIFO算法。
时钟页面置换算法:现实的
    将所有的页面都保存在一个类似钟面的环形链表中,一个表针指向最老的页面。
    遇到R位是0的则淘汰该页面并把新页面插入该位置,表针向前移动一个位置,如果R位为1则清零,并向前继续搜索。
最近最少使用(LRU,Least Recently Used)页面置换算法:很优秀,但很难实现
    困难的是每次访问内存都必须更新整个链表
    一种方法:每个页面都有一个计数值,如果访问了该页面则加1,计数值最小的则是最近最少使用的页面。
    一种方法:用一个初值为0的n*n位的矩阵,访问到页框k时,首先把k行的位都设置成1,再把k列的位都设置成0。则任何时刻,二进制数值最小的行就是最近最少使用的。 !!!!!
最不常用(NFU,Not Frequently Used)算法:LRU的相对粗略的近似
    每次时钟中断,都将页面的R位加到计数器上,这个计数器大体上跟踪了各个页面被访问的频繁程度,发生缺页中断时则置换计数值最小的页面。
    一个页面被频繁访问后,即使很长时间没被访问其计数值仍被遗留下来并影响了后面的置换操作。
老化(aging)算法:非常近似LRU的有效算法,是对最不常用算法的改进:
    调整:在R位被加进之前先将计数器右移一位,并且R位加到计数器的最左端而不是最右端。  
    发生缺页中断时,仍是置换计数器值最小的页面。
    与LRU算法的区别:
        1)如果两个页面的计数值相同,老化算法并不知道哪个页面是先被访问的。
        2)老化算法的计数器只有有限位数,限制了对以往页面的记录。假设为8位,那如果两个页面的计数器都是0,有可能其中一个页面上次被访问是在9个时钟滴答前,而另一个是在1000个时钟滴答前,但我们不知道,只能随机选一个页面置换。
工作集页面置换算法:一个进程当前正在使用的页面的集合称为它的工作集(working set)。
    请求调页(demand paging)策略。
    局部性访问行为:在进程运行的任何阶段,它都只访问较少的一部分页面。并且大多数程序也不是均匀地访问它们的地址空间的。
    若每执行几条指令程序就发生一次缺页中断,那么就称这个程序发生了颠簸(thrashing)。
    不少分页系统都会设法跟踪进程的工作集,以确保在让进程运行以前,它的工作集已在内存中了,该方法称作工作集模型(working set model)。目的在于减少产生缺页中断的次数,在让进程运行前预先装入其工作集页面也称为预先调页(prepaging)。
    预先调页就是在程序继续运行之前预先装入推测出的工作集的页面。
    定义:工作集就是最近K次内存访问所使用过的页面的集合。
    置换算法:当发生缺页中断时,淘汰一个不在工作集中的页面   ===>   需要一种精确方法确定哪些页面在工作集中。
工作集算法:实现起来开销很大
工作集时钟算法:好的有效算法

3.5 分页系统中的设计问题

如何在互相竞争的可运行进程之间分配内存:局部分配策略与全局分配策略。
	全局算法在通常情况下工作得比局部算法好,当工作集的大小随进程运行时间发生变化时这种现象更加明显。
	工作集的大小可能在几微秒内就会发生改变,而老化位却要经历一定的时钟滴答数才会发生变化。
	另一种途径:定期确定进程的数目并为他们分配相等的份额。
		不合理,30K和300K的进程都分配一样的份额?
		对每个进程都规定一个最小的页框数。
		根据进程大小按比例分配页面,但是必须在程序运行时动态更新。
	使用全局分配策略时,如何为每个进程分配页面的数量。
	管理动态内存的一种方法是使用PFF(Page Fault Frequency,缺页中断率)算法。
		测量缺页中断率:计算每秒的缺页中断数。
		金控制分配集的大小。
		PFF尽力让每个进程的缺页中断控制在可接受的范围内。
	注意一些页面置换算法适用于局部置换算法也适用于全局置换算法,而一些则只采用局部策略才有意义。
负载控制:减少系统发生颠簸,将进程交换到磁盘中。
	将进程交换出去以减轻内存需求的压力是借用了两级调度的思想,在此过程中一些进程被放到磁盘,此时用一个短期的调度程序来调度剩余的过程。
页面大小
	小页面的好处:
		进程数据如内存段、数据段等不会恰好装满整个页面而导致有些页面部分为空的浪费称为内部碎片(internal fragmentation)。
		与小页面相比,大页面使更多没有用的程序保留在内存中。程序的分阶段运行。
	大页面的好处:
		页面小意味着更大的页表。内存与磁盘的交换传输中的大部分时间都花在了寻道和旋转延迟上,所以小页面和大页面的传输时间是基本相同,则小页面会更耗时。
	开销是页表和内部碎片的损失。
		在页面比较小的时候,第一项(页表大小)大,而页面设置比较大的时候,则第二项(内存碎片)大。
		如何选择较优的页面大小。根据公式的结果选择出来。
分离的指令空间和数据空间:就是分成了代码段(I空间)和数据段(D空间)等。
	地址空间小使得程序员对地址空间的使用出现困难。
	当硬件进行取指令操作时,要知道使用I空间和I空间页表,类似的,对数据的访问必须通过D空间页表。
共享页面:指令空间的页面共享,但是需要对应的数据结构记录这些共享页面。
	多个不同的用户运行同一个程序是很常见的,通过共享页面避免在内存中有一个页面的两份副本。
	I空间和D空间的分离使多个进程来共享程序变得简单。
	进程使用相同的I空间页表和不同的D空间页表。
		每个进程在它的进程表中都有两个指针:一个指向I空间,一个指向D空间页表。
	多个进程共享代码时,需要考察一个页面是否被共享,为了防止A需要的页面被B替换了。
	共享数据,如fork后父进程和子进程都共享程序文本和数据:
		让这些进程分别拥有它们自己的页表,但都指向同一个页面集合,这些页面被共享并且被标记为只读。
		写时复制:如果两个进程只读,则不作更改,若其中一个进程更新写操作,则会触发只读保护并引发操作系统陷阱,然后生成一个自己的专用副本。(所以前面有讲到第一次写操作也会触发陷阱)。
共享库:
	现代操作系统中有很多大型库被众多进程使用,如果用上面共享页面的粒度实现共享,将库静态与磁盘上每个程序绑定,将会使他们变得更加庞大。
	未定义外部函数(undefined externals):任何在目标文件中被调用但是没有被定义的函数。
		任何被未定义外部函数调用但是不存在的函数也会成为未定义外部函数。
	传统(静态库)的链接方式:
		链接器会在某些库如/usr/lib/libc.a中寻找未定义函数,如果找到了则加载到可执行二进制文件中。
		当链接器完成任务后,一个可执行二进制文件被写到磁盘,包括所有需要的函数,而库中定义但是没被调用的函数则不会被加载进去。
		还是会存在磁盘空间和内存空间的大量浪费。
	共享库的链接方式:
		链接器没有加载加载被调用的函数,而是加载了一小段能够在运行时绑定被调用函数的存根例程(stub routine)。
	共享库不是一次性读入内存,而是根据需要以页面为单位进行加载的。
		如果其他进程已经装载了某个共享库,则不会再重新装载。
	共享库具有使可执行文件更小、节省内存空间外,共享库修改后不需要重新编译调用对应函数的程序。
		商业软件就可以使用共享库,而不需要分发源码给客户。
	位置无关代码(position-independent code):只使用相对偏移量的代码。
		共享库带来问题:同个共享库可能被不同的进程定位在不同的地址上,导致进程1和进程2调用同一个库函数时,重定位得到的需要跳转的地址不一样(注意这里说的是虚拟地址)。
		在编译共享库的时候,通过特殊编译选项让编译器不要产生使用绝对地址的指令,而只使用相对地址。
内存映射文件(memory-mapped file):
	共享库实际上是一种更为通用的机制————内存映射文件的一个特例。
	机制思想:进程一个通过发起一个系统调用,将一个文件映射到进程的虚拟地址空间的一部分。
	提供了一种I/O的可选模型:这可以把文件当内存中的大字符数组来使用,而不需要通过读写操作来访问这个文件。
	如果两个或两个以上的进程同时映射了同一个文件,则它们可以通过共享内存来通信。
清除策略:如何清除系统中被使用的页框,从而保存一定数量的空闲页框。分页守护进程(paging daemon)。保存一定数目的页框。清除策略方法之一是使用一个双指针时钟,前指针用于将脏指针协会磁盘,后指针用于页面置换。
虚拟内存接口。
	允许程序员对内存映射进行控制,并可以通过非常规的方法来增强程序的行为。
	共享内存实现高性能的消息通信系统。进程控制页面映射。
	分布式共享内存。

3.6 有关实现的问题

与分页有关的工作,操作系统要在下面四个时间段里做与分页相关的工作
	进程创建时
		要确定程序和数据在初始化时有多大,并为它们创建一个页表,要在内存中为页表分配空间并对其进行初始化。也就是说页表是一开始就得分配好的,这也就是为什么二级页表可以节省内存的问题了,如果使用二级页表则不需要一次性先分配那么多的空间?
		页表在进程运行时必须在内存中。
		操作系统要在磁盘交换区中分配空间。
		操作系统要用程序正文和数据对交换区进行初始化,这样当新进程发生缺页中断时,可以调入需要的页面。
	进程执行时
		必须为新进程设置MMU,刷新TLB,以清除以前的进程遗留的痕迹。
		PC(程序计数器)所指的页面是必须装入内存的。
	缺页中断时
		操作系统必须通过读硬件寄存器来确定是哪个虚拟地址造成了缺页中断。
		计算需要的页面并在磁盘上定位,找到合适页框存放新页面,置换新老页面。
		备份程序计数器,重新执行引起缺页中断的指令。
	进程终止时
		释放进程的页表、页面和页面在硬盘上所占用的空间。
		如果某些页面是与其它进程共享的,则最后一个使用它们的进程终止时才可以释放内存和硬盘上的页面。
缺页中断处理。缺页中断发生时的事件顺序如下:
	1)硬件陷入内核,在堆栈中保存程序计数器。大多数机器将当前指令的各种状态信息保存在特殊的CPU寄存器中。
	2)启动一个汇编代码例程保存通用寄存器和其他易失的信息,以免被操作系统破坏。这个例程将操作系统作为一个函数来调用。
	3)当操作系统发现一个缺页中断时,尝试发现需要哪个虚拟页面。通常一个硬件寄存器包含了这一信息,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析这条指令看看它在缺页中断时正在做什么。
	4)知道发生缺页中断的虚拟地址后,操作系统检查地址是否有效并检查存取与保护是否一致。不一致则向进程发出信号或杀死该进程。如果地址有效且没有保护错误发生则检查是否有空闲页框,没有的话则执行页面置换算法寻找一个页面来淘汰。
	5)如果选择的页框“脏”了,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其他进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。
	6)一旦页框“干净”后(无论是立刻还是在写回磁盘后),操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入。该页面被装入后,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一个用户进程运行。
	7)当磁盘中断发生时,表明该页已经被装入,页表已经更新可以反映它的位置,页框也被标记为正常状态。
	8)恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。
	9)调度引发缺页中断的进程,操作系统返回调用它的汇编语言例程。
	10)该例程恢复寄存器和其他状态信息,返回到用户控件继续执行,就好像缺页中断没有发生过一样。
指令备份。如何重新启动引起陷阱的指令。
	情况一:一条指令通常涉及多次内存访问,则操作系统需要弄清发生缺页中断时的程序计数器值对应位置的字是内存地址还是指令的操作码。
		引起陷阱的可能是指令的操作数,而这时候需要记录的是操作数前面的操作指令地址。
	情况二:更糟糕的情况是有些体系结构的寻址方式采用自动增量,增量可能在内存访问前完成也可能在访问之后完成。则需要在重启该指令时考虑是否要将软件中的寄存器减量从而得到正确的值。
	一种解决方法:使用一个隐藏的内部寄存器,在每条指令执行之前,把程序计数器的内容复制到该寄存器。
	操作系统的设计者也需要考虑解决这个问题。
锁定内存中的页面。
	分页算法是全局算法则有很小机会包含缓冲区的页面被选中换出内存,如果一个I/O设备正处在对页面进行DMA传输的过程中将该页面移出,会导致部分缓冲数据被写入到最新装入的页面中。
	应当避免将包含I/O缓冲区的页面选中换出内存。
	解决方法:
		1)锁住正在做I/O操作的内存中的页面以保证它不会被移出内存。锁住一个页面通常称为在内存中钉住(pinning)页面;
		2)另一个中方法是在内核缓冲区中完成所有的I/O操作,然后再将数据复制到用户页面。(现在似乎都是这种方法?)
后备存储。
	页面被换出时存放在磁盘上的哪个位置。
	在磁盘上分配页面空间的最简单算法是在磁盘上设置特殊的交换分区,甚至从文件系统划分一块独立的磁盘(以平衡I/O负载)
		分区中没有普通的文件系统,始终使用相应分区的起始块号。
	方法一:新进程启动后,在交换分区中被分配与其核心映像同等大小的交换分区,与每个进程对应的是其交换区的磁盘地址,即进程映像所保存的地方,这一信息记录在进程表里。
		写回页面时计算写会地址简单,虚拟地址空间中页面偏移量加到交换区开始地址即可。
		进程启动前必须初始化交换区,也可以将整个进程映像复制到交换区。
		同样存在进程启动后可能增大的问题:为正文、数据和堆栈区分别保留交换区,并且允许这些交换去在磁盘上多于一个块。
	方法二:实现什么也不分配,在页面换出时为其分配磁盘空间,并在换入时回收磁盘空间。
		缺点是内存中每个页面都要记录相应的磁盘地址,即每个进程都必须有一张表,记录每一个页面在磁盘上的位置。
		在磁盘映射表项中包含一个非法的磁盘地址或者一个表示它们未被使用的标记位。
	不能保证总能够实现固定的交换分区,没有磁盘分区可用时怎么处理。
		可以利用正常文件系统中的一个或多个较大的、事前定位的文件。
		程序正文通常是只读的,当内存资源进程时可以丢弃它们,需要的时候再从可执行文件中读入。
策略和机制的分离。
	控制系统复杂度的一种重要方法就是把策略从机制中分离出来。通过使大多数存储管理器作为用户级进程运行,就可以把该原则应用到存储管理中。
	例子,存储管理系统被分为三个部分:
		1)一个底层MMU处理程序
			封装所有MMU工作的细节,与机器相关。
		2)一个作为内核一部分的缺页中断处理程序
			进程运行出现缺页中断时由缺页中断程序找出需要哪个虚拟页面,发送消息给外部页面调度程序通知发生了什么问题,然后再由外部页面调度程序从磁盘中读入所需页面。
		3)一个运行在用户空间中的外部页面调度程序
			一个进程启动时,需要通知外部页面调度程序以便建立进程页面映射,如果需要的话还要在磁盘上分配后备存储。
			进程运行时可能要把新对象映射到它的地址空间,还需要再次通知外部页面调度程序。
			原则:无法访问所有页面的R位和M位,由缺页中断告诉它淘汰页面及对应的信息。
	更多的模块化代码和更好的适应性,但由于多次交叉“用户-内核”边界引起额外的开销,以及系统模块间消息传递所造成的额外开销。

3.7 分段

待添加。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章