1、绪论java
操做系统的各类内存管理策略都出于同一个目的:同时将多个进程存放在内存中,以便容许多道程序设计。不过,这些策略都须要在进程执行以前将整个进程放在内存中。动态载入虽然能减轻这个限制,但须要程序员当心应用,而且花费额外的工做。程序员
而虚拟内存则容许执行进程部分在内存中,一个显著的优势是程序能够比物理内存大。并且虚拟内存将内存抽象成一个巨大的数组,将用户视界的逻辑内存与物理内存分离,使得程序员不受内存存储的限制。简而言之,虚拟内存展示在程序员面前的是一个比物理内存要大得多的、地址连续的内存空间,而事实上是映射到支离破碎的物理内存,乃至磁盘上。算法
然而,虚拟内存的实现并不容易,使用不当反而可能大大地下降性能。数据库
2、按需调页数组
一、基本概念缓存
页须要用到的时候才调入内存。数据结构
这种方案须要硬件支持区分哪些页在内存,哪些在磁盘。采用有效/无效位来表示。当页表中,一个条目的该位为有效时,表示该页合法且在内存中;反之,可能非法,也可能合法但不在内存中。app
当进程试图访问这些还没有调入内存的页时,会引发页错误陷阱(page-fault trap)。按如下步骤进行处理:性能
1)检查进程内部页表,一般与PCB一块儿保存。以肯定该引用的合法性spa
2)若是非法,进程终止;不然进行调入:
3)找到一个空闲帧
4)调度一个磁盘操做,将所需页调入刚分配的帧
5)磁盘读操做完成后,修改内部表和页表(有效无效位?),表示该页已在内存中
6)从新开始因陷阱而中断的指令。
二、按需调页的性能
对于按需调页,下降页错误率相当重要。
另外是对交换空间的处理的使用。磁盘IO到交换空间一般比到文件系统要快,由于交换空间是按大块进行分配,并不使用文件查找和间接分配方法。所以,在进程开始时将整个文件镜像复制到交换空间,并从空间交换执行按页调度,那么有可能得到更好的性能。
另外一种选择是开始时从文件系统进行按需调页,但置换出来的页写入交换空间,然后的调页则从交换空间中读取。这种方法确保只有须要的页才从文件系统中调入,又能够保证必定的性能。
3、写时复制
有些进程,好比fork()出来的子进程,并不须要按需调页,而是一开始与父进程共享页面,当子进程须要修改页的时候,才对该页复制一个副本,在副本上进行修改。是为写时复制。
当一个页须要写时复制的时候,从哪里分配空闲页很重要。许多操做系统为此提供空闲缓冲池。
4、页面置换
内存有时会过分分配,进程须要使用的页大于可分配内存;加上内存并不只用于进程的页,IO缓存也须要使用大量的内存,会出现内存相对需求僧多粥少的局面,这时进程发生页错误的时候,操做系统准备好了要调入的所需页,却发现没有空闲帧可供分配。正所谓房子看好了,车也看好了,一切都看双色球了。
一、页置换
遇到这种状况,操做系统能够选择终止该嗷嗷待哺的进程,也能够交换出一个倒霉的进程。更多的时候,会采用页置换的方式:
若是没有空闲帧,就查找当前没有使用的帧,将其释放,空出来保存进程出错的页(也就是须要换入的页)。
若是换出的页有修改的话,还必须将页写回磁盘。能够经过设置修改位或脏位来提升性能。
页置换算法:
二、FIFO页置换
最简单的页置换算法。选择最旧的页进行置换。具体为建立一个FIFO队列来管理内存中的全部页,队列中的首页被置换,而新调入的页则加到队列的尾部。
FIFO算法容易理解和实现,但性能不老是很好。所替代的页可能仍在使用,换出去之后立刻报页错误,要求换回来。
三、最优置换
置换最长时间不使用的页(不是久未使用,而是预测其将来通过最长时间才被使用?)。这种算法页错误率最低。
这种算法问题在于难以实现。
四、LRU页置换
最优置换的近似。最优置换与FIFO的关键区别在于,FIFO使用的是页调入时间,而最优置换看重的是页未来使用的时间。若是使用离过去最近做为不远未来的近似,那么可置换最长时间没有使用的页。根据过去来猜想将来。这种方法称为 最近最少使用算法。
实现LRU算法,可用计数器,也可用栈:凡用过的页,就放到顶部,不用的就沉到栈底。
五、近似LRU页置换
不多有计算机系统能提供足够的硬件来支持真正的LRU页置换。然而,许多系统经过引用位方式来进行近似置换:
页表内的每一个条目都关联一个引用位,每当引用一个页时,相应的引用位就被硬件置位;
刚开始时,全部引用位都清零,后来许多被置为1。经过检查引用位,能够知道哪些页使用过而哪些没有。这个信息是近似LRU置换算法的基础。
近似LRU置位算法有几种:
1)附加引用位算法
每页有一个8位的字节作引用位,按期刷新引用位。有引用的时候该字节最高位置1,其余位右移,挤掉原来的最低位。那么,引用位为最小值的页就能够被置换。
2)二次机会算法
当一个倒霉的页被选中时,检查其引用位,若是为0,直接置换掉;若是引用位为1,就给它一次机会,放过它,继续找下一张倒霉页。那张得到重生机会的页,其引用位清零,重置时间。在全部页都被寻找过一遍以前,它起码不会被替换掉。
3)加强型二次机会算法
经过将引用位和修改位做为一有序对来考虑:
(0,0)最近无使用也无修改:换吧,别犹豫了
(0,1)最近无使用但有修改:置换前要写回磁盘,请三思!
(1,0)最近有使用但无修改:可能很快又要使用
(1,1)最近有使用且有修改:可能很快又要使用,且置换前要写回磁盘,请三思!
六、基于计数的页置换
为每一个页设置一个计数器,造成两种方案
1)最不常用页置换算法(LFU)
置换计数最小页。理由是活动页应该有更大的引用次数。但可能有以下问题:一个页可能开始时使用不少,但之后就再也不使用。解决方法是按期将次数寄存器右移一位,以造成指数衰减的平均使用次数。
2)最常使用页置换算法(MFU)
置换计数最大页。理由:最小次数页可能刚刚调进来,且还没使用。
七、页缓冲算法
保留一个空闲帧缓冲池。
1)维护一个已修改页的列表。每当调页设备空闲时,就选择一个修改页写到磁盘上,并重置其修改位。这种方案增长了干净页,下降了置换时写出的几率。
2)保留一个空闲帧池,记住页与帧的对应关系。当帧须要重用时,就先从池中取,没有磁盘IO。
八、应用程序与页置换
有时,应用程序经过操做系统使用虚拟内存结果会更坏。数据库就是一个例子。由于数据库可提供本身的内存管理和IO缓冲,由于它更能理解本身的内存使用和磁盘使用。基于此,操做系统容许特殊程序将磁盘当成逻辑块数组使用,而无需经过操做系统的文件系统。
5、帧分配
如何在各个进程之间分配必定的空闲内存?
简单办法是将帧挂在空闲帧链表上,当发生页错误之时即进行分配。进程终止时帧再次放回空闲帧链表。
帧分配策略受到多方面限制。例如, 分配数不能超过可用帧数,也必须分配至少最少数量。保证最少许的缘由之一是性能。页错误增长会减慢进程的执行。而且,在指令完成前出现页错误,该指令必须从新执行。因此有足够的帧相当重要。
每一个进程帧的最少数量由体系结构决定,而最大数量是由可用物理内存数量决定。
一、帧分配算法有
1)平均分配,每一个进程同样多
2)按进程大小使用比例分
3)按进程优先级分
4)大小和优先级组合分
二、全局分配和局部分配
全局置换容许进程从全部帧集合中选择一个进行置换,而无论该帧是否已分配给其余进程,即它能够从其余进程抢夺帧,好比高优先级抢夺低优先级的帧;局部分配则要求每一个进程只能从本身的分配帧中分配。
全局置换一般有更好的吞吐量,且更为经常使用。
6、系统颠簸
进程若是没有它所须要的帧,那么很快产生页错误,这时必须置换某个页。然而全部页都在使用,置换一个,马上又要换回来,页错误频繁在发生,称为颠簸。
颠簸致使严重的性能问题。操做系统时刻注视CPU的使用率,若是CPU使用率过低,系统会引入新进程。采用全局置换算法,可无论页属于哪一个进程,抢到就换。假设一个进程须要更多帧,开始出现页错误,从其余进程抢到帧。被抢的进程从就绪队列移出,CPU使用率降低;CPU调度程序发现后,调入更多进程,企图让CPU嗨起来。新进来的进程嗷嗷待哺,帧被抢夺得更激烈,等待队列更长,CPU使用率进一步降低,CPU调度程序更努力地调入更多的进程。。。
最终,进程主要忙于调页,系统不能完成一件工做。
使用局部置换能够限制系统颠簸,但不能彻底解决这个问题。
一、工做集合模型
为了防止颠簸,进程必须得到足够多的帧才能够启动。操做系统跟踪每一个进程的工做集合,为其分配大于其工做集合的帧数。若是还有空闲,才有可能启动另外一进程。若是某个进程全部工做集合之和超过了可用帧总数,那么会被暂停,其帧分配给其余进程。挂起的进程等待之后重启。此为工做集合模型。困难在于跟踪工做集合。
二、页错误频率策略
除了工做集合,另外一种防止颠簸的方案是页错误频率策略。
若是一个进程,页错误频率过高,说明须要更多的帧,给它!若是页错误频率过低,说明帧有富余,分些给别人。为进程设置页错误率上下限,机动地分配帧。
与工做集合模型同样,若是须要帧却无帧可分配,那么进程应该暂停,释放给其余一样高页错误频率的进程。
7、内存映射文件
一般,文件每次访问都须要一个系统调用和磁盘访问,但还有另外一种方法:使用虚拟内存技术将文件IO做为普通内存进行访问。意思就是说,访问文件就像访问内存同样。
一、基本机制
将磁盘块映射成内存页(一页或多页)。刚开始时,页面调度,会产生页错误,这样,文件内容陆续读入物理内存矣。文件的读写就像内存访问同样,经过内存操做文件而不是系统调用read()和write(),从而简化。
其中,对文件的写可能不会当即写到磁盘上,除非脏页置换或操做系统按期检查,或者文件关闭?
若是一个文件多个进程共用,那么将其映射到各自的虚拟内存中,以容许数据共享。任一进程修改虚拟内存中的数据,其余进程均可以见到。若是有修改,则是修改各自的副本,写时复制。可能还有互斥。
二、WIN32 API 的共享内存
将存在于磁盘的文件放进一个进程的虚拟地址空间,并在该进程的虚拟地址空间中产生一个区域用于“存放”该文件,这个空间就叫作File View(存放在进程的虚拟内存中),系统并同时产生一个File Mapping Object(存放于物理内存中)用于维持这种映射关系,这样当多个进程须要读写那个文件的数据时,它们的File View其实对应的都是同一个File Mapping Object,这样作可节省内存和保持数据的同步性,并达到数据共享的目的。
三、内存映射IO
将IO设备映射到内存,那么对该部份内存进行读写,就如同对IO设备进行读写,而没必要直接操做IO设备。好比说,屏幕上每个点都对应一个内存地址,程序控制内存,就能控制屏幕显示。
8、内核内存的分配
当用户态进程须要额外内存时,能够从内核所维护的空闲页帧链表中获取页。一般,页帧分散在物理内存中,可是内核内存一般从空闲内存池中获取,主要由两个缘由:
1)内核须要为大小不一样的数据结构分配内存,所以必须节省使用,并尽可能减低碎片浪费。许多操做系统的内核代码与数据不受分页系统控制
2)有的硬件须要直接与物理内存打交道,而不通过虚拟内存接口,所以须要内存常驻在连续的物理页中
内核进程进行内存管理的两个方法:
一、Buddy系统
从物理上连续、大小固定的段上进行分配,按2的幂大小来进行分配,如4K、8K等。优势是可经过合并而快速造成更大的段,但容易产生碎片。
二、slab分配
按照内核对象的数据结构要求的大小,预先分配好若干内存块,等待召唤使用。
具体来讲,内核对象对应有高速缓存,而高速缓存含有若干个slab(就是尺寸合适的内存块?)。slab可有三种状态:满的、空的、部分。当分配的时候,先从空闲状态部分分配,不够从空的部分分配;还不够就从物理连续页上分配新的。
优势:
1)尺寸因应内核对象要求可变,没有碎片
2)预先准备,可快速知足要求
9、其余考虑
一、预调页
纯按需调页的一个显著特性是当一个进程开始时会出现大量页错误。而预调页的策略是同时将所需的全部页调入内存。关键是成本是否小于相应页错误的成本。
二、页大小
该用大页仍是小页,是个问题。
1)大页有利于减小页表
2)小页有利于减小碎片,可更好地利用内存
3)小页传输快,大页IO好,但又不必定,小页由于寻址、传输快,局部性得以改善,总的IO就会下降,那么,应该用小页?
4)然而,大页能够下降页错误数量
……
切克闹,如今你告诉我,该用大页仍是小页?
三、TLB范围
TLB可提升内存访问速度,若是没有TLB,则每次取数据都须要两次访问内存,即查页表得到物理地址和取数据。
TLB只维护页表中的一小部分条目,逻辑地址转换物理地址过程当中,先在TLB中查找,若是找到,那么物理地址唾手可得;若是TLB中没有,那么使用置换算法,将相关条目置换进TLB,而后再获得物理地址。
那么提升TLB命中率相当重要。
提升TLB命中率可增长TLB条数,但代价不小,由于用于构造TLB的相关内存既昂贵又费电。另外一个方法是增长页的大小,或提供多种页大小。
四、反向页表
反向页表能够节省内存,不过,当进程所引用的页不在内存中时,仍然须要一个外部页表以得到物理帧保存哪一个虚拟内存页面的信息。所幸这只是在页错误时才须要用到,外部页表自己能够换出换入,不苛求必定完备。
五、程序结构
咱们日常写程序,对内存根本不用关心。但有时了解一点内存知识可改善系统性能:
比方说,有一个128*128的二维数组,数据按行存放,如何遍历性能高?
int i,j; int[128][128] data;
for(int j=0;j<128;j++) for(int i=0;i<128;i++) data[i][j] = 0;
但假如这样写:
for(int i=0;i<128;i++) for(int j=0;j<128;j++) data[i][j] = 0;
六、I/O互锁
容许页在内存中被锁住。
在全局置换算法中,一个进程发出IO请求,被加入到IO设备等待队列,而CPU交给了其余进程。这些进程发生页错误,恰恰置换了等待进程用于IO的缓存页,这些页被换出。好了,请求IO的进程等待到了IO设备,针对指定地址进行IO,然而帧早被其余进程的不一样页所使用。
对这个问题,一般有两种解决方法:
1)毫不对用户内存进行IO,若是要进行IO,将用户内存数据复制到系统内存。要复制一次,开销过高了。
2)物理帧有一个锁住位,容许页锁在内存中。若是锁住,则不能置换。当IO完成,页被解锁。
锁住位用处多多,好比操做系统内核页一般加锁;低优先级进程的页至少要运行一次才能解锁被置换。