内存管理包括:内存管理概念、交换与覆盖、连续分配管理方式和非连续分配管理方式(分页管理方式、分段管理方式、段页式管理方式)。
虚拟内存管理包括:虚拟内存概念、请求分页管理方式、页面置换算法、页面分配策略、工作集和抖动。
操作系统对内存的划分和动态分配
寄存器,高速缓存,主存,磁盘缓存属于操作系统存储管理,掉电后其信息不再存在
辅存和可移动存储介质 属于设备管理(因此会涉及中断,设备驱动程序和物理设备的运行),存储的信息被长期保存
为了使程序能够运行,必须先为之创建进程,而创建进程的第一件事,就是将程序和数据装入内存,如何将一个用户源程序变为一个可在内存中执行的程序,通常要经过如下几步,首先是编译(由编译程序将用户源代码编译成若干个目标模块),其次是链接(由链接程序将编译后形成的一组目标模块,以及它们所需要的库函数链接在一起,形成一个完整的装入模块),最后是装入(由装入程序将装入模块装入内存)
如果在编译时知道程序驻留在内存的什么位置,那么,编译程序将产生绝对地址的目标代码,绝对装入方式按照装入模块中的地址,将程序和数据装入内存,装入模块被装入内存后,由于程序中的逻辑地址与实际内存地址完全相同,故不需要对程序和数据的地址进行修改。
由于绝对装入方式只能将目标模块装入到内存中事先指定的位置,在多道程序环境下,编译程序不可能事先知道所编译的目标模块应放在内存的何处,因此,绝对装入方式只适用于单道程序环境,在多道程序环境下,所得到的目标模块的起始地址通常都是以0开始的,程序中的其他地址也都是相对于起始地址计算的,此时应采用可重定位装入方式,根据内存的当前情况,将装入模块装入到内存的适当位置。该方式会使装入模块中的所有逻辑地址与实际装入内存的物理地址不同,需要对数据地址和指令地址进行修改,通常把在装入时对目标程序中指令和数据的修改过程称为重定位,又因为地址变换通常是在装入时一次完成的,以后不再变化,故称为静态重定位。
可重定位装入方式允许将装入模块装入到内存中任何允许的位置,故可用多道程序环境,但这种方式并不允许程序运行时在内存中移动位置,因为,程序在内存中的移动,意味着它的物理位置发生了变化,这就必须对程序和数据的地址进行修改后方能运行。然而,在运行过程中它在内存中的位置可能经常要改变,此时就应该采用动态运行时装入方式。动态运行时的装入程序在把装入程序装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址都仍是逻辑地址,为了使地址转换不影响指令的执行速度,需要重定位寄存器的支持。
1.静态链接:在程序运行之前,先将各目标模块及他们所需的库函数, 链接成一个完整的装配模块,以后不再拆开 2.装入时动态链接:将用户源程序编译后所得到的一组目标模块, 在装入内存时,采用边装入边链接的链接方式 3.运行时动态链接:对某些目标模块的链接,是在程序执行中需要该模块时,才对它进行链接
在将几个目标模块装配成一个装入模块时。须解决两个问题:
(1)对相对地址进行修改
(2)变换外部调用符号 。
比如:三个目标模块A,B,C。合为一个时,B的起始地址变为L,C->(L+M),因为编译程序产生的所有目标模块中,使用的都是相对地址,其起始地址都为0,每个模块中的地址都是相对于起始地址计算的。而外部符号 B,C要变为起始地址L和L+M
用户源程序经编译后所得是目标模块,是在装入内存时边装入边链接的,即在装入一个目标模块时,若发生一个外部模块调用事件,将引起装入程序去找出相应的外部目标模块,并将它装入内存,装入时动态链接有如下优点,便于修改和更新(各目标模块是分开的存放的,所以要修改或更新各目标模块非常容易),便于实现对目标模块的共享(很容易将一个目标模块链接到几个应用模块上,实现多个应用程序对该模块的共享)
将某些模块的链接推迟到程序执行时才进行链接,即在执行过程中,当发现一个被调用模块尚未装入内存时,立即由OS去找到该模块并将之装入内存,把它链接到调用者模块上,凡在执行过程中未被调用到的模块,都不会被调入内存和被链接到装入模块上,这样不仅加快程序的装入过程,同时也节省了大量的内存空间。
最简单的一种存储管理方式,但只能用于单用户、单任务的操作系统中。
采用这种存储管理方式时,可把内存分为系统区和用户区两部分,系统区仅提供给OS使用,通常是放在内存的低址部分;
用户区是指除系统区以外的全部内存空间,提供给用户使用。
在早期的单用户、单任务操作系统中,有不少都配置了存储器保护机构,用于防止用户程序对操作系统的破坏。但在近年来常见的单用户操作系统中,都未采取存储器保护措施。一方面可以节省硬件,另一方面因为这是可行的。
在单用户环境下,机器由一用户独占,不可能存在其他用户干扰的问题,这时可能出现的破坏行为也只是用户程序自己去破坏操作系统,其后果并不严重,只是会影响该用户程序的运行,且操作系统也很容易通过系统的再次启动而重新装入内存。
固定分区分配是一种最简单的可运行多道程序的存储管理方式,将内存用户空间划分为若干个固定大小的区域,在每个分区只装入一道作业,这样,便允许多道作业并发执行,当有空闲分区时,便可以再从外存的后备作业队列中选择一个适当大小的作业装入该分区,当该作业结束时,又可再从后备作业队列中找出另一作业调入该分区。
对于内存的用户空间的划分,有如下两种方法。
① 分区大小相等,即所有的内存分区大小相等。缺点是缺乏灵活性,即当程序太小时,会造成内存资源的浪费,程序太大时,一个分区由不足以装入该程序,只是该程序无法运行。
② 分区大小不等,把内存区划分成含有多个较小的分区、适量中等分配和少量大分区,这样,便可根据程序的大小为之分配适当的分区。
为了便于内存分配,将分区按大小进行排队,并为之创建一张分区使用表,其中各表项包括每个分区的起始地址、大小、状态(是否已分配),当有一个程序需要装入时,由内存分配程序依据用户程序大小检索该表,从中找出一个能满足要求的,尚未分配的分区,将之分配给该程序,然后将该表项中的状态设置为已分配,若未找到大小足够的分区,则拒绝为该用户分配内存。
为了实现分区分配,胸中必须配置相应的数据结构,用来描述空闲分区和已分配分区的情况,为分配提供依据,常用的数据结构有如下两种形式:空闲分区表(在系统中设置一张空闲分区表,用于记录每个空闲分区的情况,每个空闲分区占一个表目,表目中包括分区序号、分区始址、分区大小等,在前面已有介绍)、空闲分区链(为了实现对空闲分区的分配和链接,在每个分区的起始部分,设置一些用于控制分区分配的信息,以及用于链接各分区所用的向前指针;在分区尾部设置一向后指针,这样,可以将空闲分区链接成一个双向链),为了检索方便,在分区尾部重复设置状态为和分区大小表目,当分区被分配出去以后,把状态为从0改成1,此时前后指针都失去意义(已经不再空闲链表中)
N+2 = N+1+1 ,也就是算上了指针的大小
说明:size表示事先规定的不再切割的剩余分区的大小。空闲分区表示为m.size,请求分区的大小为u.size。(从该分区中划出u.size()的大小的时候剩余的部分依然留在空闲分区表或者链中)
当进程运行完毕释放内存时,系统根据回收区的首址,从空闲区链(表)中找到相应的插入点,此时会出现如下四种情况之一:
1. 回收分区与插入点的前一个空闲区F1相邻接,此时将回收区与插入点的前一分区合并,不必为回收区分配新表项,只需要修改前一分区F1的大小。
2. 回收分区与插入点的后以空闲分区F2相邻接,此时将两分区合并,形成新的空闲分区,用回收区的首址作为新空闲区的首址,大小为两者之和。
3. 回收区同时与插入点的前、后两个分区邻接,此时将三个分区合并,使用F1的表项和F1的首址,取消F2的表项,大小为三者之和。
4. 回收区既不与F1邻接,也不与F2邻接,这时为回收区单独建立一个新表项,填写回收区的首址和大小,并根据其首址插入到空闲链中的适当位置。
在连续分配方式中,必须把一个系统或用户程序装入一连续的内存空间,若果在系统中只有若干个小的分区,即使他们容量总和大于要装入的程序,但由于这些分区不相邻接,也无法把该程序装入内存。若想装入,则将内存中的所有作业进行移动,使他们全部相邻接,这样,即可把原来分散的多个小分区拼接成一个大分区,这时,就可以把作业装入该区。经过紧凑后的某些用户程序在内存中的位置发生了变化,此时若不对程序和数据的地址加以修改(变换),则程序必将无法执行,为此,在每次紧凑之后,都必须对移动了的数据和程序进行重定向。
在动态运行时装入的方式中,作业装入内存后的所有地址都仍然是相对地址,将相对地址转化为物理地址的工作,退推迟到程序指令要真正执行时进行。为了是地址变换不影响指令的执行速度,在系统中增设了一个重定位寄存器,用它来存放程序(数据)在内存中的起始地址。在程序执行时,真正访问的内存地址是相对地址与重定位寄存器中的地址相加而形成的。该动作是随着对每条指令或数据的访问自动进行的,故称为动态重定位,当系统对内存进行了紧凑而使若干程序在内存中移动时,不需要对程序做任何修改,只要用该程序在内存的新起始地址去置换原来的起始地址即可。
与动态分区分配算法基本上相同,差别仅在于:在这种分配算法中,增加了紧凑功能,通常,在找不到足够大的空闲分区来满足用户需求时进行紧凑,如果空间够了,就进行分配即可,如果不够,就返回错误信息 。
对换的单位有两种:
1. 整个进程(对换过程称之为进程对换/整体对换,作为处理机中级调度) 2. 进程的一个页面或者一个段(称之为页面对换/分段对换,又统称为部分对换)
这里只说进程对换,部分对换在讲到虚拟存储的时候再阐述。为了实现进程对换,系统必须实现对换空间的管理、进程的换出、进程的换入。
在具有对换功能的OS中,通常把外存分为文件区和对换区,前者用于存放文件,后者用于存放从内存换出的进程。由于文件通常是较长久的驻留在外存上,文件区的管理主要目标是提高存储空间的利用率,采取离散分配方式,进程通常在对换区中驻留的时间较短暂,对换操作较频繁,故对对换空间管理的主要目标是提高进程换入和换出的速度,采取的是连续分配的方式,较少考虑外存中的碎片问题 。
其他说明:对于对换空间的管理方式,还是采用之前的空闲分区表或者空闲分区链的方式 。分配与回收也与之前相同。
每当进程由于创建子进程等原因而需要更多的内存空间,但又无足够的内存空间情况时,系统应将某进程换出,首先,系统选择处于阻塞状态且优先级最低的进程作为换出进程,然后启动磁盘,将该进程的程序和数据传送到磁盘的对换区上,若传送过程没有错误,则可回收该进程所占用的内存空间,并对该进程的进程控制块等做相应的修改。要注意的是:1.在对进程换出时,只能换出非共享的程序和数据段,而对于共享的那些,只要有进程需要就不能被换出。2.如果内存中还有可以换出的进程,就继续执行换出操作,直到内存中再无阻塞进程为止。
对换进程定时执行换入操作,首先查看所有进程的状态,从中找出就绪状态但已换出的进程,将其中换出时间最久的进程作为换入进程,将其换入,直至无换入的进程或无可换出的进程为止。
分页存储管理是将一个进程的逻辑地址空间分成若干个大小相等的片,称为页面或页,并为各页进行编号,从0开始。相应地,把内存空间分成与页面相同大小的若干个存储块,称为(物理)块或者页框,也同样为它们编号,如0#块,1#块等。在未进程分配内存时,以块为单位将进程的若干个页分别装入到多个可以不相邻接的物理块中,由于进程的最后一页经常装不满一块而形成不可利用的碎片,称之为页内碎片。
在分页系统中的页面其大小应适中,页面若太小,一方面可以使内存碎片减少,有利于提高内存利用率,但是,每一个进程占用的页面较多,导致页表过长,占用太多内存,会降低页面换进换出的效率。页面若太大,可减少页表的长度,提供页面换进换出的速度,但是,内存碎片会增大,所以,也页面大小应适中,通常为1KB~8KB 。
分页地址中的地址结构如下:
说明:前一部分为页号P,后一部分为位移量W(或称为页内地址,也就是页有多大,也就是物理块的大小),总共32位,其中0~11位为页内地址,每页大小4KB(2^12),12~31位为页号,地址空间最多允许有1M (2^20个)页
为了能够保证在内存中找到每个页面所对应的物理块,系统为每个进程建立了一张页面映射表,简称为页表。页表项纪录了相应页在内存中对应的物理块号,在配置了页表后,进程执行时,通过查找该表,即可找到每页在内存中的物理块号,页表实现了从页号到物理块号的地址映像。
即使在简单的分页系统中,也常在页表的表项中设置一存取控制字段,用于对该存储块中的内存加以保护,当存取控制字段仅有一位时,可用来规定该存储块中的内存时允许读/写,还是只读;若存取控制字段为二位,则可规定为读/写、只读、只执行等存取方式 。如果要用它来实现虚拟存储器,还需要有一个数据项。
为了能够将用户地址空间中的逻辑地址变换为内存空间中的物理地址,在系统中必须设置地址变换机构,该机构的基本任务是实现从逻辑地址到物理地址的转换,由于页内地址与物理块内的地址一一对应,无须再进行转换,因此,地址变换机构的任务实际上只是将逻辑地址中的页号转换为内存中的物理块号。又因为页面映射表的的作用就是用于实现从页号到物理块号的变换,因此,地址变换任务是借助页表来完成的。
页表大多驻留在内存。在系统中只设置一个页表寄存器PTR(Page-Table Register),用于存放页表在内存的始址和页表的长度(也就是有几个页表项),平时,进程未执行时,页表的始址和页表长度存放在本进程的PCB中,当调度程序调度到某进程时,将这两个数据装入页表寄存器,因此,在单处理机环境下,虽然系统中可以运行多个进程,但只需要一个页表寄存器。
当进程要访问某个逻辑地址中的数据时,分页地址变换机构会自动地将有效地址(相对地址)分为页号和页内地址两部分,再以页号为索引去检索页表,查找操作由硬件执行,在执行检索前,先将页号与页表长度进行比较,若页号大于或等于页表长度,则表示本次访问的地址超越了进程的地址空间,这一错误将被系统发现并产生一个地址越界中断。若未出现错误,则将页表始址加上页号与页表项长度的乘积,便得到该表项在页表中的位置,于是可从中得到该页的物理块号,将之装入物理地址寄存器,与此同时,再将有效地址寄存器中的页内地址送入物理地址寄存器的块内地址字段中,这样,便完成了逻辑地址到物理地址的转换。
现代计算机系统中,可以支持非常大的逻辑地址空间(2^32~2^64),这样,页表就变得非常大,要占用非常大的内存空间,如,具有32位逻辑地址空间的分页系统,规定页面大小为4KB,则在每个进程页表中的页表项可达1M(2^20)个,又因为每个页表项占用一个字节,故每个进程仅仅页表就要占用1MB的内存空间,而且要求连续,这显然是不现实的,可以通过如下两个方法解决该问题。
① 采用离散分配方式来解决难以找到一块连续的大内存空间的问题。 ② 只将当前需要的部分页表项调入内存,其余页表项仍驻留在磁盘上,需要时再调入。
对于要求连续的内存空间来存放页表的问题,可利用将页表进行分页,并离散地将各个页面分别存放在不同的物理块中的办法来解决,同样的,也要为离散分配在页表再建立一张页表,称为外层页表。在每个页表项中记录了页表页面的物理块号,以32位逻辑地址空间为例进行说明。
说明:外层页号P1为10位,可以表示1024个物理块,外层页表中的外层也内地址P2为10位,可以表示1024个物理块,页内地址为12位,表示页面大小为4K。
说明:在页表的每一个表项中存放的是进程的某页在内存中的物理块号,如第0页的0页存放1#物理块,第1页存放4#物理块,而在外层页表的每个页表项中,所存放的是某页表分页的首址,如第0页页表存放在1011#物理块中,第1页页表存放在1078#物理块中。(也就是内页存放物理块的地址,外页存放内页地址)
为了实现地址变换,在地址变换机构中需要增设一个外层页表寄存器,用于存放外层页表的始址,并利用逻辑地址中的外层页号,作为外层页表的索引,从中找到指定页表分页的始址,在利用P2作为指定页表分页的索引,找到指定的页表项,其中即含有该页在内存的物理块号,用该块号和页内地址d即可构成访问的内存物理地址。
将页表施行离散分配的方法,虽然解决了对大页表无需大片存储空间的问题,但是并未解决用较少的内存空间去存放大页表的问题,换言之,只用离散分配空间的办法并未减少页表所占用的内存空间,解决办法是把当前需要的一批页表项调入内存,以后再根据需要陆续调入。在采用两级页表结构的情况下,对于正在运行的进程,必须将其外层页表调入内存,而对页表则只需要调入一页或者几页,为了表征某页的页表是否已经调入内存,还应在外层页表项中增设一个状态位S,其值若为0,表示该页表分页尚未调入内存,否则,说明已经在内存,进程运行时,地址变换机构根据逻辑地址P1,去查找外层页表,若所找到的页表项中的状态位为0,则产生一中断信号,请求OS将该页表分页调入内存。
对于64位的机器而言,采用两级页表已经不太合适,如果页面大小仍采用4KB,那么剩下52位,若还是按照物理块的大小(2^12位)来划分页表,每个页表项4B,故一页中可存放2^10个页表项,则将余下的42位用于外层页号,此时,外层页表中可能有4096G个页表项,要占用16384GB的连续内存空间,显然是不行的。必须采用多级页表,即将外层页表再进行分页。若计算机的虚拟地址空间大小为2^64,页面大小为4KB,页表项为4B,则最少页表的级数为6级,首先总的页面个数为2^52(64 - 12),其次,每个物理块能装入的页表项为4KB/4B = 2^10个,10 * 6 > 52,即最少需要6级。
从固定分区到动态分区分配,再到分页存储管理方式,其主要动力为提高内存利用率,引入分段存储管理的目的在于满足用户在编程和使用上多方面的要求。如
① 方便编程,用户可以把自己的作业按照逻辑关系划分为若干段,每个段都是从0开始编址,并有自己的名字和长度。
② 信息共享,在实现对程序和数据的共享时,是以信息的逻辑单位为基础的,在分页中可能共享数十个页面,这样就会很不方便。而在分段中就可以为共享的数据编写一个独立的段。
③ 信息保护,信息保护同样是对信息的逻辑单位进行保护。
④ 动态增长,在实际应用中,数据段在使用过程中往往会不断增长,而实现无法确切知道数据段会增长到多大,分段可以较好的解决这个问题。
⑤ 动态链接,在运行时,先将主程序所对应的目标程序(即段)装入内存并启动运行,当运行过程中有需要调用某段时,才将该段调入内存并进行链接。
在分段管理中,作业的地址空间被划分为若干个段,每个段定义了一组逻辑信息。如有主程序段MAIN,子程序段X,数据段D及栈段S,每个段都有自己的名字,每个段从0开始编址,并采用一段连续的地址空间,段的长度由相应的逻辑信息组的长度决定,因而各段长度不等,整个作业的地址空间由于是分成多个段,因而是二维的,即其逻辑地址由段号和段内地址构成。
说明:一个作业允许最长有64K个段,每个段的最大长度为64KB。
在分段式存储管理系统中,为每个分段分配一个连续的分区,而进程中的各个段可以离散地移入内存中不同的分区,为了使程序正常运行,能够物理内存中找出每个逻辑段所对应的位置,应该为每个进程建立一张段映射表,称为段表,每个段在表中有一个表项,其中记录了该段在内存中的起始地址和段的长度。段表可以存放在一组寄存器中,这样有利于提高地址转换速度,但通常将段表放在内存中。段表用于实现从逻辑段到物理内存区的映射。每个进程一个段表,存放在内存(也就是说段表属于该进程)
为了实现从进程的逻辑地址到物理地址的变换功能,在系统中设置了段表寄存器,用于存放段表始址和段表长度TL(也就是有多少个段表项),在进行地址变换时,系统将逻辑地址中的段号与段表长度TL进行比较,若S>TL,表示段号太大,访问越界,产生越界中断信号,若未越界,则根据段表的始址和该段的段号,计算该段对应段表项的位置,从中读出该段在内存中的起始地址,然后,再检查段内地址d是否超过该段的段长SL,若超过,同样发出越界中断信号,若未越界,则将该段的基址与段内地址d相加,即得到要访问的内存物理地址。
每次访问一个数据时(需给出段号和段内地址),也需要访问两次内存,第一次根据段号获得基址,第二次根据基址与段内地址之和访问真实数据的物理地址。这降低了计算机的速率,也可以增设一个联想存储器,用来保存最近常用的段表项,用来加速存取数据的时间。
① 页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率,或者说,分页仅仅是由于系统管理的需要而不是用户的需要,段则是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了能更好地满足用户的需要。
② 页的大小固定且由系统决定,由系统把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,一个系统中,只存在一种大小的页面,段的长度则不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时,根据信息的性质来划分。
③ 分页的作业的地址空间是一维的,即单一的线性的地址空间,程序员只利用一个记忆符即可表示一个地址,而分段的作业地址空间是二维的,程序员在标识一个地址是,需要给出段名和段内地址。
分页系统能够有效的提高内存利用率(但是会存在页内碎片),分段系统则能够很好地满足用户需要。若能将两种方式结合起来,既具有分段系统的便于实现、分段可共享、易于保护、可动态链接等优点,又能像分页系统那样很好地解决内存的外部碎片问题,基于此,提出了段页式系统。
段页式系统先将用户程序分成若干个段,再把段分为若干个页,并为每一个段赋予一个段名。段页式系统中,地址结构由段号、段内页号、页内地址三部分构成。
在段页式系统中,为了便于实现地址转换,须配置一个段表寄存器,其中存放段表始址和段表长TL,进行地址变换时,首先利用段号S,将它与段表长TL进行比较,若S < TL,表示未越界,于是利用段表始址和段号来求出该段所对应的段表项在段表中的位置,从中得到该段的页表始址,并利用逻辑地址中的段内页号P来获得对应页的页表项位置,从中读出该页所在的物理块号b,再利用b和页内地址构成物理地址。
在段页式系统中,为了获得一条指令或数据,需要访问内存三次,第一次访问时访问内存中的段表,从中取得页表始址,第二次访问是访问内存中的页表,从中取出该页所在的物理块号,并将该块号与页内地址一起形成指令或数据的物理地址,第三次访问才是真正的从第二次访问所得的地址中,取出指令或数据。同样,也可以增设高速缓冲寄存器用于加快访问速度。
https://www.cnblogs.com/leesf456/p/5616041.html
操作系统总结之内存管理(除虚拟内存管理)