第6章 存 储 管 理
6.1 存储管理概述
操作系统中用于管理内存空间的模块称为内存管理模块,它负责内存的全部管理工作,具体地说就是要完成4个功能,即存储空间的分配、存储地址的变换、存储空间的保护以及存储空间的扩充。
6.1.1 内存的分配与回收
内存分配是为进入系统准备运行的程序分配内存空间,内存回收是当程序运行结束后回收其所占用的内存空间。为实现此功能,系统须跟踪并记录所有内存空间的使用情况,按照一定的算法为进程分配和回收内存空间。
存储分配方案主要包括以下要素:
(1) 描述存储分配的数据结构:系统需采用某种数据结构(表格、链表或队列等)来登记当前内存使用情况以及空闲区的分布情况,供存储分配程序使用。在每次分配或回收操作后,系统都要相应地修改这些数据结构以反映这次分配或回收的结果。
(2) 实施分配的策略:确定内存分配和回收的算法。好的算法应既能满足进程的运行要求,又能充分利用内存空间。
分配策略及相关数据结构的设计直接决定存储空间的利用率以及存储分配的效率,因而对系统的整体性能有很大的影响。
6.1.2 地址变换
由于用户在编写程序时无法预先确定程序在内存中的具体位置,所以只能采用逻辑地址进行编程。而当程序进入内存后,必须把程序中的逻辑地址转换为程序所在的实际内存地址。这一转换过程称为存储空间的地址变换,或称为地址映射。地址变换是由内存管理模块与硬件的地址变换机构共同完成的。
1. 地址的概念
1) 符号地址
在用高级语言编写的源程序中,我们使用符号名(变量名、函数名、语句标号等)来表示操作对象或控制的转移地址。比如用变量名代表一个存储单元、用函数名代表函数的入口地址、用语句标号代表跳转地址等。这些符号名的集合称为符号名空间。因此,高级语言程序使用的地址空间是符号名空间,编程者不需考虑程序代码和数据的具体存放地址。
例如,以下所示的是一个C源程序的片段:
main()
{ int i=1;
…
i++;
…
}
此源程序中没有具体地址,只有符号名。这里main代表的是程序的入口地址,i代表的是一个数据的存放地址。
2) 逻辑地址
编译程序将源代码中的语句逐条翻译为机器指令,为每个变量分配存储单元,并用存储单元的地址替换变量名。这些指令和数据顺序存放在一起,从0开始编排地址,形成目标代码。目标代码所占有的地址范围称为逻辑地址空间,范围是0~n-1,n为目标代码的长度。逻辑地址空间中的地址称为逻辑地址,或称为相对地址。在访问内存的指令中用逻辑地址来指定一个操作数的地址,在跳转指令中用逻辑地址来表示要跳转到的那条指令的地址。
例如,对上例所示的源程序进行编译,生成的目标代码的反汇编结果如下:
00000000: …
…
0000004B: LDS R24,0x0060 ;从0060地址取数据,加载到R24
寄存器
0000004D: ADIW R24,0x01 ;R24寄存器内容加1
0000004E: STS 0x0060,R24 ;将R24寄存器内容写回0060地址
…
00000060: 0x0001 ;i变量的存储单元
…
左侧列出的是指令和数据的逻辑地址,从0地址开始顺序排列。i变量被分配到逻辑地址0060处,i++语句被译为LDS、ADIW和STS 3条指令,它们排在逻辑地址004B、004D和004E处。在目标代码的指令中已看不到符号名了,而代之以具体的地址值。如LDS和STS指令的操作数地址是0060,表示要到这个地址(也就是i变量)读/写数据。
3) 物理地址
物理内存由一系列的内存单元组成,这些存储单元从0开始按字节编址,称为内存地址。当目标程序加载到内存中时,它所占据的实际内存空间就是它的物理存储空间,物理空间中的地址称为物理地址,或称为绝对地址。
每次程序加载时所获得的实际地址空间取决于系统当时的运行状态,因而是不确定的。但物理地址空间不会是从0开始的,因为系统内存的低端地址通常被操作系统占用。由此可看出,程序的逻辑地址空间与物理地址空间是不同的。由于编译程序无法预知程序执行时的实际内存地址,所以目标程序中的地址都是从0开始的逻辑地址,而实际地址只有在程序加载时才能得知。
假设上面例子的程序加载到内存,它分配到的内存地址空间是从1024(即十六进制的0x0400)开始的,则程序中各条指令和变量的地址是原来的相对地址加上1024这个基址。因此程序在内存的起始地址为0x0400,LDS、ADIW和STS 3条指令的绝对地址分别为0x044B、0x044D和0x044E,i变量的绝对地址为0x0460。
图5-1所示是关于内存地址的示意图。仍以前面的程序为例,源程序中的i变量是用符号名i标识的一个存储单元,它没有具体的地址值。i++语句的操作就是对这个存储单元进行的操作。编译时,编译程序为i分配了具体的存储单元,并用该单元的编号地址96(0x0060)替换掉所有i符号名。程序在加载时获得实际的内存空间。如果得到的内存空间的起始地址是1024,则程序中的相对地址96单元就是实际内存的1120(0x0460)单元。
![](https://i-blog.csdnimg.cn/blog_migrate/4af115a64d266c2d0a8c1948572d606b.png)
2. 地址变换
用户编程时只能使用逻辑地址,而CPU执行指令时必须指定物理地址,因此必须在指令执行前进行地址变换,将指令中的逻辑地址转换为CPU可直接寻址的物理地址,这样才能保证CPU访问到正确的存储单元。
假设上面的例子程序加载到内存,它分配到的内存地址空间是从1024开始的,则程序中各条指令和变量的地址都是原来的相对地址加上1024。为了适应这个变化,指令中引用的操作数地址也应进行相应的调整。下面所示是经过地址变换后的目标代码,粗体部分为变换后的操作数的绝对地址。
00000400: …
…
0000044B: LDS R24,0x0460 ;从0460地址取数据,加载到R24
寄存器
0000044D: ADIW R24,0x01 ;R24寄存器内容加1
0000044E: STS 0x0460,R24 ;将R24寄存器内容写回0460地址
…
00000460: 0x0001 ;i变量的存储单元
…
地址变换的方式有两种:
(1) 静态地址变换:程序在装入内存前一次性完成地址转换。程序装入内存后即可直接执行。DOS系统中的程序就是采用这种方式加载的。采用静态地址变换的程序在内存中始终处于最初加载的位置,不可移动。
(2)动态地址变换:程序在装入内存时不进行地址变换,而是保持指令中的逻辑地址不变。在程序执行过程中,每执行一条指令时,如果指令中用到了逻辑地址,地址变换机构就会自动进行地址转换,将逻辑地址变换为实际地址。例如,当CPU取到LDS R24, 0x0060指令时,先将0060这个逻辑地址变换为绝对地址0460,然后再执行LDS R24, 0x0460指令。
动态地址变换的特点是程序在内存中可移动、可共享。但是动态地址变换需要有硬件的支持,不过目前PC机以上档次的计算机都具有动态地址变换的硬件机构。
6.1.3 内存的保护
内存保护的含义是要确保每个进程都在自己的地址空间中运行,互不干扰,尤其是不允许用户进程访问操作系统的存储区域。对于允许多个进程共享的内存区域,每个进程也只能按自己的权限(只读或读/写)进行访问,不允许超越权限进行访问。
许多程序错误都会导致地址越界,比如使用了未赋值的“野”指针或空指针等。还有一些程序代码则属于恶意的破坏。存储保护的目的是为了防止因为各种原因导致的程序越界和越权行为。为此,系统必须设置内存保护机制,对每条指令所访问的地址进行检查。一旦发现非法的内存访问就会中断程序的运行,由操作系统进行干预。现代操作系统都具有良好的存储保护功能,因此程序错误通常只会导致程序的异常结束,而不会造成系统的崩溃。
常用的存储保护措施有:
(1) 界限保护:在CPU中设置界限寄存器,限制进程的活动空间。
(2) 保护键:为共享内存区设置一个读/写保护键,在CPU中设置保护键开关,它表示进程的读/写权限。只有进程的开关代码和内存区的保护键匹配时方可进行访问。
(3) 保护模式:将CPU的工作模式分为用户态与核心态。核心态下的进程可以访问整个内存地址空间,而用户态下的进程只能访问在界限寄存器所规定范围内的空间。
6.1.4 内存的扩充
尽管内存容量不断提高,但相比应用规模的增长来说,内存总是不够的。因此,内存扩充始终是存储管理的一个重要功能。
“扩充”存储器空间的基本思想是借用外存空间来扩展内存空间,方法是让程序的部分代码进入内存,其余驻留在外存,在需要时再调入内存。主要的实现方法有以下3种:
1. 覆盖技术
覆盖(overlay)技术的原理是将一个程序划分为几个模块。程序的必要模块(主控或常用功能)常驻内存,其余模块共享一个或几个存储空间。它们平时驻留在外存中,在需要时才装入内存,覆盖掉某个暂时不用的模块。
覆盖技术的缺点是必须在编程时对程序进行模块划分,并确定程序模块之间的覆盖关系。这无疑增加了编程的复杂度。
2. 交换技术
在多个程序并发执行时,往往有一些程序因等待某事件而暂时不能运行。如果将暂时不能执行的程序换到外存中,就可以获得空闲内存空间来运行别的程序。这就是交换(swapping)技术的思想。与覆盖技术不同的是,交换是以进程为单位进行的。
交换技术的优点是增加了可并发运行的程序数目,且对用户的程序结构没有要求。其缺点是对整个进程进行的换入、换出操作往往需要花费大量的CPU时间。
3. 虚拟存储器
以上两种存储扩充技术都不能称为虚拟存储技术,因为在用户(编程者)眼里看到的还是实际大小的内存。虚拟存储(virtual memory)的原理是只将程序的部分代码调入内存,其余驻留在外存空间中,在需要时调入内存。程序代码的换入和换出完全由系统动态地完成,用户察觉不到。因此,用户看到的是一个比实际内存大得多的“虚拟内存”。
虚拟存储技术的特点是方便用户编程,存储扩充的性能也是最好的。关于虚拟存储器的介绍见6.3节。
6.2 存储管理方案
随着操作系统的发展,内存管理技术也在不断地发展着。本节将简要介绍各种存储管理方案的技术和特点。
6.2.1 单一连续存储管理
最简单的存储器管理方案是在内存中只存放一个应用程序,这个应用程序和操作系统共享存储器。这是最简单的一种存储管理方式,只能用于单用户、单任务的操作系统中。DOS系统采用的就是这种方式。
单一连续存储管理方式的分配策略是将内存分成两个分区,称为系统区和用户区,如图5-2所示。系统区仅供操作系统和硬件使用,用户区供给用户程序使用,它只能容纳一个程序。地址变换方式是静态地址变换,即程序加载前一次性完成全部地址变换。存储保护措施采用界限保护,即不允许用户程序访问操作系统区域,否则就发生地址越界中断。存储扩充的手段只能采用覆盖技术。
![](https://i-blog.csdnimg.cn/blog_migrate/bd1318b6d8c28f1352411e51c7cf964d.png)
单一连续存储管理的特点是简单,它只适用于单道程序系统。
6.2.2 分区存储管理
多道程序系统的出现要求内存中能同时容纳多个程序,分区管理方案因而诞生。分区分配是多道程序系统最早使用的一种管理方式,其思想是将内存划分为若干个分区,操作系统占用其中一个分区,其他分区由用户程序使用,每个分区容纳一个用户程序。
1. 简单分区管理
1) 分区分配策略
最初的分区划分方法是固定分区,即系统把内存静态地划分为若干个固定大小的分区。当一个进程被建立时,系统按其程序的大小为其分配一个足够大的分区。由于分区大小是预先划分好的,通常会大于程序的实际尺寸,因此分区内余下的空闲空间就被浪费掉了。图5-3(a)所示为固定分区的内存分配方式。
对固定分区分配策略进行改进就产生了可变分区分配。它的思想是:在程序调入内存时,按其实际大小动态地划分分区。这种量体裁衣的分配方式避免了分区内空间的浪费。图5-3(b)所示为可变分区的内存分配方式。
![](https://i-blog.csdnimg.cn/blog_migrate/6c5d61b8a02f455afe5cea389ffb83b1.png)
分区分配的方法是:系统设置两个表格来记录内存空间的使用情况。一个是已分配分区表,说明各个已用分区的分区号、起始地址和大小等。另一个是空闲区表,说明内存中空闲区的分布位置和大小。当为进程分配空间时,系统在空闲区表中找到一个合适的空闲区,按进程需要的大小为它划分出一个分区,从空闲分区表中扣除,加入到已分配分区表中。释放分区时将其从已分配分区表中删除,插入或并入空闲区表中。
2) 分区分配的碎片问题
分区分配的主要问题是存储“碎片”。碎片(fragment)是无法被利用的空闲存储空间。固定分区存在“内部碎片”问题,即遍布在各个分区内的零碎剩余空间。可变分区存在“外部碎片”问题,即随着进程不断地进入和退出系统,一段时间后,内存中的空闲分区会变得支离破碎,这些碎片空间的总和可能足够大,但因为不连续,所以不能被利用。图描述了外部碎片的产生过程。
假设系统最初有进程1、2和3在运行,它们的映像的大小分别为320 KB、224 KB和288 KB,所占据的内存分区如图5-4(a)所示。一段时间后,进程2运行结束退出,进程4进入内存,它的大小是128 KB,此时内存的分区情况如图(b)所示。又一段时间后,进程1运行结束退出,进程5进入内存,它的大小是220 KB。此时内存的分区情况如图5-4(c)所示。当一个新的300 KB的进程6想要进入系统运行时,内存中的空闲空间的总数虽然足够,但因为是碎片,所以系统暂时无法接纳这个作业。
![](https://i-blog.csdnimg.cn/blog_migrate/b4f45dcdbc392547d5dbfcf8701a47af.png)
2. 可重定位分区管理
可重定位分区管理的思想是在可变分区分配的基础上利用存储紧缩技术来消除碎片。
1) 存储紧缩技术
解决外部碎片问题的一个有效方法是存储紧缩技术。存储紧缩(compaction)的思想是采用动态地址变换,使程序在内存中可以移动。当内存出现碎片现象时,系统将暂停所有进程的运行,将各个进程的分区向内存一端移动,从而将碎片合并成一个连续的存储空间。紧缩完成后,程序继续运行。
2) 动态地址变换过程
简单分区采用静态地址变换方式,程序装入内存后就不能再移动了。因为程序移动后,指令和数据的存放地址变了,而指令中的操作数地址却没有相对地变化,导致指令不能正确地寻址。为了使程序在内存中可以移动,就必须采用动态地址变换。
可重定位分区的动态地址变换过程如图5-5所示。
![](https://i-blog.csdnimg.cn/blog_migrate/842a84b4442b8647044cc5370b14765b.png)
CPU中设置了一对表示程序存储空间界限的寄存器,长度寄存器中存放的是程序的长度,基址寄存器中存放的是程序所占内存空间的起始地址。每个进程的PCB中都有一对相应的寄存器值,当进程得到CPU准备运行时,现场恢复操作会将这两个值装入寄存器中。当CPU取到一条指令时,硬件地址变换机构将逻辑地址与基址寄存器内容相加就可得到实际内存地址。
每次存储紧缩完成后,系统根据程序的新位置更新各个进程的基址值。这样,当程序重新运行时,CPU将按新的基址来做地址转换,程序的运行不会受到任何影响。
3. 分区的保护与扩充
进程只能在自己的分区内活动。存储保护的方式是上下界地址保护,即进程运行时,它的空间上下界地址被加载到CPU的界限(或基址/长度)寄存器中。如果进程试图访问超越分区上下界的地址,则会引起地址越界中断,使进程结束。
在分区管理中,用户程序的大小受可用分区大小的限制。可以使用覆盖或交换技术来实现内存扩充。
总的来说,分区管理的特点是简单、支持多道程序,但有碎片问题。可重定位分区管理提供了解决碎片问题的一种途径,因而提高了存储空间的利用率。但存储紧缩比较耗时,在进行存储紧缩时,所有用户进程都要停止运行,系统为此付出的代价过大。
6.2.3 页式存储管理
产生碎片问题的根源在于程序要求连续的存储空间,而解决这一问题的根本措施就是突破这一限制,使程序代码可以分散地存放在不同的存储空间中。分散存储使得内存中每一个空闲的区域都可以被程序利用,这就是页式存储分配的基本思想。
1. 分页的概念
分页(paging)的概念是:将程序的逻辑地址空间分成若干大小相等的片段,称为页面(page),用0、1、2…序号表示;同时,把内存空间也按同样大小分为若干区域,称为块,或页帧(page frame),也用0、1、2…序号表示。
经过分页后,程序的逻辑地址可看成由两部分组成,即页号+页内位移。对i386体系结构来说,逻辑地址为32位,页面大小为4 KB,则逻辑地址的高20位为页号,低12位为页内位移,如图5-6所示。例如,有一个逻辑地址为十六进制0001527A,则其页号为十六进制15,页内位移为十六进制27A。
![](https://i-blog.csdnimg.cn/blog_migrate/cd9d12607a5d50fcd58069bfb66867c0.png)
2. 页式分配思想
页式分配的思想是以页为单位为程序分配内存,每个内存块装一页。一个进程的映像的各个页面可分散存放在不相邻的内存块中,用页表记录页号与内存块号之间的映射关系。图5‑描述了这种分配方式。
页表是进程的一个重要资源,它记录了进程的页面与块号的对应关系。例如在图5-7中,进程A的程序代码被划分为4页,分别加载到内存的第9、10、3和5块中,进程B的程序代码被划分为3页,分别加载到内存的第7、8和11块中,它们的页表如图5‑7所示。虽然它们都不是连续存放的,但通过页表可以得到分散的各块的逻辑顺序。
![](https://i-blog.csdnimg.cn/blog_migrate/fbf445c5f593f75c3a75862314c77d95.png)
3. 页面的分配与释放
系统设有一个内存块表,记录系统内所有物理内存块的分配和使用状况。内存块表可采用位示图的方式或空闲块链表方式表示。位示图用一系列的二进制位来描述各个内存块的状态,每个位对应一个内存块,0表示空闲,1表示占用。空闲链是用拉链的方式来组织空闲的内存块的。系统根据内存块表进行存储分配和释放,每次分配和释放操作后都要相应地修改此表。
不考虑虚拟存储技术时,页式的分配和释放算法都比较简单。当进程建立时,系统根据进程映像的大小查找内存块表,若有足够的空闲块则为进程分配块,为其建立页表并将页表信息填入PCB中。若没有足够的空闲块,则拒绝进程装入。进程结束时,系统将进程占用的内存块回收,并撤销进程的页表。
4. 页式地址变换
页式系统采用动态地址变换方式,通过页表进行地址变换。每个进程有一个页表。用逻辑地址的页号查找页表中对应的表项即可获得该页表所在的内存的块号。页表通常存放在内存中,页表的长度和内存地址等信息记录在进程的PCB中。另外,在CPU中设有一个页表寄存器,用来存放正在执行的进程的页表长度和内存地址。当进程进入CPU执行时,进程的页表信息被填入页表寄存器,CPU根据页表寄存器的值即可找到该进程的页表。
当CPU执行到一条需要访问内存的指令时,指令操作数的逻辑地址被装入逻辑地址寄存器,分页地址转换机构会自动地进行地址转换,形成实际的内存地址。CPU随后对此地址进行访问操作。地址转换的过程是:将逻辑地址按位分成页号和页内位移两部分,再以页号为索引去检索页表,得到该页号对应的物理块号。将页内位移作为块内位移与块号拼接即得到实际的内存地址。图5-8描述了这一地址变换过程。
![](https://i-blog.csdnimg.cn/blog_migrate/bf0b0b51b49d3a5cf34a5fc02a17b355.png)
设系统的页面大小为4 KB,CPU的当前指令要访问的逻辑地址为10372,则该地址对应的页号为2(10372/4k的商),页内位移为2180(10372/4k的余数)。经查页表后,页号2变换为块号4,块内位移为2180,得到实际地址为18564(4*4k+2180)。
页表存储在内存中。CPU为了访问一个内存单元需要两次访问内存,第一次是查页表,第二次完成对内存单元的读/写操作。这显然降低了带有访问内存操作的指令的执行速度。为缩短查页表的时间,系统通常使用快表技术,就是将一些常用的页表表项保存在CPU内部的高速缓存中。存在高速缓存中的页表称为快表,快表的访问速度比内存页表的访问速度要高得多。当进行地址转换时,先用页号去查快表,查到则直接进行地址转换,未查到时则去内存查页表,再进行地址转换,同时将此页对应的页表项登记到快表中。
6. 页式存储的保护与扩充
页式存储的保护是通过控制访问地址的页号来实现的。在地址转换前,硬件将页号与页表长度进行比较,如果没有超出页表长度,则进行地址转换,否则产生地址越界中断信号。限制对共享页面的访问操作的方法是:在页表中增加一个读/写权限字段,只有当此权限与该页的设置相匹配时方可访问,否则产生读/写保护中断。
页式存储管理的存储扩充功能是通过页式虚拟存储器来实现的,具体介绍见6.3节。
页式存储管理是目前大部分系统所采用的内存管理方案。页式管理的优点是解决了内存碎片问题,有效地利用了内存,使存储空间的利用率大大地提高。不过页式管理也有“页内碎片”问题,即程序的最后一页不一定正好放满,空余的部分成为了碎片。不过页内碎片平均为每个程序半页,约2 KB左右,这个数目是可以接受的。另外,页式管理需要硬件具备页式地址变换机构。
6.2.4 段式存储管理
在分区和页式存储管理中,程序的地址空间是一维连续的。然而,从用户的观点来看,一维的程序结构有时并不理想。比如,按模块化设计准则,一个应用程序通常划分为一个主模块、若干个子模块和数据模块等。划分模块的好处是可以分别编写和编译源程序,并且可以实现代码共享、动态链接等编程技术。段式存储分配就是为了适应用户对程序结构的需求而设计的存储管理方案。
1. 段的概念
在段式存储管理系统中,程序的地址空间由若干个大小不等的段组成。段(segment)是逻辑上完整的信息单位,划分段的依据是信息的逻辑完整性以及共享和保护等需要。分段后,程序的逻辑地址空间是一个二维空间,其逻辑地址由段号和段内位移两部分组成。
分段与分页的区别在于:段是信息的逻辑单位,长度不固定,由用户进行划分;页是信息的物理单位,长度固定,由系统进行划分,用户不可见。另外,页式的地址空间是一维的,段式的地址空间是二维的。
2. 段式分配思想
段式分配策略是以段为单位分配内存,每个段分配一个连续的分区。段与段间可以不相邻接,用段表描述进程的各段在内存中的存储位置。段表中包括段长和段起始地址等信息。图5-9描述了段式存储的分配方式。
![](https://i-blog.csdnimg.cn/blog_migrate/8d797869d8b47fde32fcd51a3c92ff71.png)
3. 段的分配与释放
段式分配对内存空间的管理类似于可重定位分区的管理方法。当进程建立时,系统为进程的各段分配一个连续的存储区,并为它建立段表。进程结束后,系统回收段所占用的分区,并撤销段表。进程在运行过程中也可以动态地请求分配或释放某个段。
4. 段式地址变换
当进程开始执行时,进程的段表信息被填入CPU中的段表寄存器。根据段表寄存器的值,CPU可以找到该进程的段表。当CPU执行到一条要访问某逻辑地址的指令时,以逻辑地址中的段号为索引去检索段表,得到该段在内存的起始地址,与逻辑地址中的段内位移相加就可得到实际的内存地址。图5-10描述了这一地址变换过程。
设CPU的当前指令要访问的逻辑地址为2段的210位移处。经查段表后,获得2段的起始地址为6200,将其与段内位移210相加,得到实际地址为6410。
![](https://i-blog.csdnimg.cn/blog_migrate/0e32e28bf34d20c13bfd2c0c4d22063e.png)
6. 段式存储的共享、保护与扩充
段式存储允许以段为单位的存储共享。段的共享就是内存中只保留该段的一个副本,供多个进程使用。当进程需要共享内存中的某段程序或数据时,只要在进程的段表中填入共享段的信息,并置以适当的读/写控制权,就可以访问该段了。
当CPU访问某逻辑地址时,硬件自动把段号与段表长度进行比较,同时还要将段内地址与段表中该段长度进行比较,如果访问地址合法则进行地址转换,否则产生地址越界中断信号。对共享段还要检验进程的访问权限,权限匹配则可进行访问,否则产生读/写保护中断。
段式存储空间的扩充采用段式虚拟存储器技术,在此不作介绍。
段式管理的特点是便于程序模块化处理,可以充分实现分段共享和保护。但由于段需要连续存储,可能出现碎片问题。另外,段式管理需要硬件具备段式地址变换机构。
6.2.5 段页式存储管理
段页式存储管理是页式和段式两种存储管理方案相结合的产物。它的分配思想是段式划分,页式存储。即把程序的各段按页式分配方式存储在内存的块中,每段一个页表。另设一个段表,指示各段的页表位置。这样就实现了程序的不连续存放。
采用段页式方式时,程序的逻辑地址可以看做是由3部分组成的,即段号+页号+页内地址。地址变换过程是:先根据段号查段表,获得该段的页表,再用页号查页表,得到实际内存块号,最后与页内地址合并即可得到实际内存地址。
段页式存储管理具备了页式和段式两种存储管理方式的优点,存储空间的利用率高,并能满足各种应用要求。但这种管理技术过于复杂,软硬件开销也很大,因此较少使用。
6.3 虚拟存储管理
6.3.1 虚拟存储技术
1. 程序的局部性原理
实验证明,在进程的执行过程中,CPU不是随机访问整个程序或数据范围的,而是在一个时间段中只集中地访问程序或数据的某一个部分。进程的这种访问特性称为局部性(locality)原理。局部性原理表明,在进程运行的每个较短的时间段中,进程的地址空间中只有部分空间是活动的(即被CPU访问的),其余的空间则处于不活动的状态。这些不活动的代码可能在较长的时间内不会被用到(比如初始化和结束处理),甚至在整个运行期间都可能不会被用到(比如出错处理)。它们完全可以不在内存中驻留,只当被用到时再调入内存,这就是虚拟存储器的思想。可以说,程序的局部性使虚拟存储成为可能。
2. 虚拟存储器原理
虚拟存储器的原理是用外存模拟内存,实现内存空间的扩充。做法是:在外存开辟一个存储空间,称为交换区。进程启动时,只有部分程序代码进入内存,其余驻留在外存交换区中,在需要时调入内存。
与覆盖技术的不同之处在于,覆盖是用户有意识地进行的,用户所看到的地址空间还是实际大小的空间;而在虚拟存储技术中,内存与交换空间之间的交换完全由系统动态地完成,应用程序并不会察觉,因而应用程序看到的是一个比实际内存大得多的“虚拟内存”。
与交换技术的不同之处在于,交换是对整个进程进行的,进程映像的大小仍要受实际内存的限制;而在虚拟存储中,进程的逻辑地址空间可以超越实际内存容量的限制。因此,虚拟存储管理是实现内存扩展的最有效的手段。
不过,读/写硬盘的速度比读/写内存要慢得多,因此访问虚拟存储器的速度比访问真正内存的速度要慢,所以这是一个以时间换取空间的技术。另外,虚拟空间的容量也是有限制的。一般来说,虚拟存储器的容量是实际内存容量与外存交换空间容量之和,这与具体的系统设置有关。但虚存容量最终要受地址寄存器位数的限制。对于32位计算机来说,32位可以表示的数字范围是4 G,因此它的虚存空间的上限就是4 GB。
3. 虚拟存储器的实现技术
虚拟存储器的实现技术主要有页式虚存和段式虚存两种,以页式虚存最为常用。本节将只介绍这种页式虚存技术。
6.3.2 页式虚拟存储器原理
页式虚拟存储器的思想就是在页式存储管理基础上加入以页为单位的内外存空间的交换来实现存储空间扩充功能。这种存储管理方案称为请求页式存储管理。
1. 请求页式管理
在请求页式管理系统中,最初只将过程映像的若干页面调入内存,其余的页面保存在外存的交换区中。当程序运行中访问的页面不在内存时,则产生缺页中断。系统响应此中断,将缺页从外存交换区中调入内存。
请求页式的页表中除了内存块号外还增加了一些信息字段,设置这些信息是为了实施页面的管理和调度,如地址变换、缺页处理、页面淘汰以及页面保护等。实际系统的页表结构会有所不同,这取决于系统的页面管理和调度策略。图5-11所示是一种典型的请求页式的页表结构。其中,“状态位”表示该页当前是否在内存;“修改位”表示该页装入内存后是否被修改过;“访问位”表示该页最近是否被访问过;“权限位”表示进程对此页的读/写权限;“外存地址”为该页面在外存交换区中的存储地址。
![](https://i-blog.csdnimg.cn/blog_migrate/0ce8f7d1d173507b14c25a9b8a31200d.png)
2. 地址变换过程
请求页式的地址变换过程增加了对缺页故障的检测。当要访问的页面对应的页表项的状态位为N时,硬件地址变换机构会立即产生一个缺页中断信号。CPU响应此中断后,将原进程阻塞,转去执行中断处理程序。缺页中断的处理程序负责将缺页调入内存,并相应地修改进程的页表。中断返回后,原进程就可以重新进行地址变换,继续运行下去了。图5-12描述了这个地址变换过程。
![](https://i-blog.csdnimg.cn/blog_migrate/c242f13f92fc63aeabb6ed9478479d08.png)
图5-13所示是一个地址变换过程的实例,其中的地址均用十六进制数字表示。设系统的页面大小为4 KB,CPU的当前指令要访问的逻辑地址为0x3080,则该地址对应的页号为3,页内位移为0x80。设进程当前的页表为图中左面的页表。由于3号页面当前不在内存,故引起缺页中断,进程被阻塞。CPU开始执行缺页中断处理程序,调度页面。中断处理的结果是2号页面被淘汰,3号页面被调入,覆盖了2号页面。修改后的页表为图中右面的页表。中断返回后原进程被唤醒,进入就绪状态。当再次运行时,重新执行上次那条指令,并成功地将逻辑地址0x3080变换为0x9080。
![](https://i-blog.csdnimg.cn/blog_migrate/30180a283ad43d815e1f0b6ad7e485fc.png)
3. 缺页中断的处理
缺页中断后,CPU暂停原进程的运行,转去执行缺页中断的处理程序。缺页中断处理程序的任务是将进程请求的页面调入内存。它先查到该页在外存的位置,如果内存中还有空闲块则将缺页直接调入。如果没有空闲块就需要选择淘汰一个已在内存的页面,再将缺页调入,覆盖被淘汰的页面。在覆盖被淘汰的页面前,先检查该页在内存驻留期间是否曾被修改过(页表中的修改位为1)。如果被修改过,则要将其写回外存交换空间,以保持内外存数据的一致性。缺页调入后,还要相应地修改进程页表和系统的内存分配表。中断处理完成后,原进程从等待状态中被唤醒,进入就绪状态,准备重新运行。
图5-14描述了缺页中断的处理过程。
![](https://i-blog.csdnimg.cn/blog_migrate/db0e2079d7ee94e3797b8dea6b818a68.png)
4. 页面淘汰算法
在缺页中断处理中,页面淘汰算法对系统的性能来说至关重要。如果淘汰算法不当,系统有时会产生“抖动(thrashing)”现象,即刚调出的页很快又被访问到,马上又被调入。抖动的系统处于频繁的页交换状态,CPU的大量时间都花在处理缺页中断上,故系统效率大幅度降低。
理论上讲,最优的算法应是淘汰以后不再访问或很久以后才会访问的页面,然而最优的算法是无法确定的。实际常用的是估计的方法,即优先淘汰那些估计最近不太可能被用到的页面。常用页面淘汰算法有以下3种:
1) 先进先出法(First-In First-Out,FIFO)
FIFO算法的思想是优先淘汰最先进入内存的页面,即在内存中驻留时间最久的页面。不过在有些时候,页面调入的先后并不能反映页面的使用情况。最先进入内存执行的代码可能也是最常用到的,比如程序的主控部分。因此,FIFO算法性能比较差,通常还要附加其他的判断来优化此算法。
FIFO算法的实现比较简单,只要用一个队列记录页面进入内存的先后顺序,淘汰时选择队头的页面即可。
2) 最近最少使用法(Least Recently Used,LRU)
LRU算法不是简单地以页面进入内存的先后顺序为依据,而是根据页面调入内存后的使用情况进行决策的。由于无法预测各页面将来的使用情况,只能利用“最近的过去”作为“最近的将来”的近似。因此,LRU算法选择淘汰在最近期间最久未被访问的页面予以淘汰。
LRU算法有多种实现和变种,其基本思想是在页表中设置一个访问字段,记录页面在最近时间段内被访问的次数或自上次访问以来所经历的时间,当须淘汰一个页面时,选择现有页面中访问时间值最早的予以淘汰。
实际运用证明LRU算法的性能相当好,它产生的缺页中断次数已很接近理想算法。但LRU算法实现起来不太容易,需要增加硬件或软件的开销。与之相比,FIFO算法性能尽管不是最好,却更容易实现。
3) 最少使用频率法(Least Frequently Used,LFU)
LFU算法是LRU的一个近似算法。它选择淘汰最近时期使用频率最少的页面。实现时需要为每个页面设置一个访问记数器(也可以用移位寄存器实现),用来记录该页面被访问的频率,需要淘汰页面时,选择记数值最小的页面淘汰。
应当指出的是,无论哪种算法都不可能完全避免抖动发生。产生抖动的原因一是页面调度不当,另一个就是实际内存过小。对系统来说应当尽量优化淘汰算法,减少抖动发生;而对用户来说,加大物理内存是解决抖动的最有效方法。
总的来说,请求页式存储管理实现了虚拟存储器,因而可以容纳更大或更多的进程,提高了系统的整体性能。但是,空间性能的提升是以牺牲时间性能为代价的,过度扩展有可能产生抖动,应权衡考虑。一般来说,外存交换空间为实际内存空间的1~2倍比较合适。
6.4 Linux的存储管理
6.4.1 Linux的内存管理概述
1. 386体系结构的内存模式
i386体系结构支持两种内存访问模式,即实模式和保护模式。当CPU中的控制寄存器CR0的PE位为0时工作在实模式,为1时是保护模式。在实模式下,只有20根地址线有效,CPU可寻址的内存地址空间只有1 GB;而在保护模式下,全部32根地址线被激活,可寻址空间达4 GB。因此,只有工作在保护模式下,才能真正发挥i386硬件的功能。i386上的所有常用操作系统(除DOS外)都是运行在保护模式下的,Linux也不例外。
i386使用的是段式管理机制,在段式管理的基础上还可以选择启用页式管理机制。当CPU中的控制寄存器CR0的PG位为1时启用分页机制,为0则不启用。
i386的页式管理机制支持两级页表,CPU能够识别页表项中的一些标志位并根据访问情况做出反应。如访问一个“存在位”(即状态位)为0的页将引起缺页中断,写一个“读/写权限位”为0的页将引起保护中断,访问页面后会自动设置“访问位”等。
2. i386的地址变换
i386的地址分为3种:逻辑地址、线性地址和物理地址。逻辑地址也称为虚拟地址,是机器指令中使用的地址。由于i386采用段式管理,所以它的逻辑地址是二维的,由段和段内位移表示。线性地址是逻辑地址经过i386分段机构处理后得到的一维地址。每个进程的线性地址空间为4 GB。物理地址是线性地址经过页式变换得到的实际内存地址,这个地址将被送到地址总线上,定位实际要访问的内存单元。
内存管理单元(MMU)是管理物理内存并进行地址变换的硬件,它完成虚拟地址到物理地址的变换。执行指令时,MMU先对指令中的虚拟地址进行段式映射,即通过“段基地址+位移量”的方式将其转换为线性地址,然后再进行页式映射,得到物理地址。
3. Linux的内存管理方案
Linux系统采用请求页式存储管理。在大多数硬件平台上(如RISC处理器),页式管理都能很好地工作。这些平台与i386系列平台不同,它们采用的是分页机制,基本上不支持分段功能。但i386体系结构在发展之初因受到PC机内存容量的限制而使用了分段的机制。Linux的设计目标之一就是良好的可移植性,它必须能适应各种流行的处理器平台,当然包括i386。
为了适应i386的段式内存管理方式,Linux巧妙地利用了共享0基址段的方式,使i386的段式映射实际上不起作用。对于Linux系统来说,虚拟地址与线性地址是一样的。Linux的所有的进程都使用同样的0~4 GB线性地址空间,这使得内存管理变得简单。
一些实时和嵌入式应用对系统的响应性要求很高,而虚存的页面调度会影响系统的实时响应性能。为解决这个问题,2.6版的内核允许编译无虚存的系统。无虚存系统实时性高,但要求有足够的内存来保证任务的执行。
6.4.2 Linux存储空间的描述
1. 页帧的描述
对于i386体系结构来说,页帧(即内存块)的大小为4 KB。1 GB的物理内存空间可划分为262 144个页帧。内核中描述页帧的数据结构是page,每个页帧对应一个page结构,所有页帧的page结构组织在一个mem_map数组中,如图5-15所示。
page结构中包含有关于该页帧的一些信息,供页面调度时使用。主要信息包括页的状态(如该页是否被修改过,是否被锁定在内存中不许换出等)、页的引用计数以及该页对应的虚拟地址。
![](https://i-blog.csdnimg.cn/blog_migrate/099d9f3494f07fba09188d299cfdb7dc.png)
2. 虚拟地址空间的描述
在i386平台上,进程的虚拟存储空间是4 GB。这4 GB的空间分为两部分:最高的1 GB供内核使用,称为“内核空间”,其中存放的是内核代码和数据,即“内核映像”;较低的3 GB供用户进程使用,称为“用户空间”。因为每个进程都可以通过系统调用进入内核,因此,内核空间由系统内的所有进程共享,而用户空间则是进程的私有空间。当进程运行在用户态时,它是在自己的用户空间内运行;当它运行在核心态时(通过系统调用),它是在内核空间运行。
进程的用户空间由5个部分组成,包括代码区、数据区、堆栈区、共享库代码区和动态分配的内存区,这些不同的区间称为虚存区。每个虚存区用一个vm_area_structs结构来描述,包含虚存区的起始和结束地址、读/写属性等信息。进程的各个虚存区的vm_area_struct结构后连成一个链表。
进程的用户地址空间由mm_struct结构描述,mm_struct结构包含进程的页目录指针(pgd)和指向虚存区链表的指针(mmap)等。在进程的PCB(task_struct结构)中包含一个mm域,它是指向mm_struct结构的指针。这些数据结构描述了一个进程的虚拟存储空间,如图5-16所示。
![](https://i-blog.csdnimg.cn/blog_migrate/d0639d119eed13b169bf13630879111a.png)
在新进程建立时,内核为其分配页表和mm_struct结构,并为其在磁盘上的映像建立虚存区,连入进程的用户地址空间。随着进程的运行,被引用的程序部分会由操作系统装入到物理内存。
6.4.3 Linux多级分页机制
Linux系统的页面大小为4 KB,则整个4 GB的线性地址空间要划分为1 M个页面。如果用一个线性页表描述的话,需要用长为1M个表项的页表才能描述。如此大的页表检索起来显然是非常低效的。
为解决这个问题,Linux系统采用了三级分页机制,即对页建立三级索引,分别称为页目录、页中间目录和页表。由于i386体系结构的限制,在i386平台上的Linux系统采用二级页索引,页目录和页中间目录合二为一,从而巧妙地适应了二级页表的硬件结构。
采用二级分页时,线性地址由三个部分组成,分别为页目录号、页表号和页内位移。线性地址的划分如图5-17所示。在32位地址中,高10位和中间10位分别是页目录号和页表号,寻址范围为1K;低12位为页内位移,寻址范围为4K。
二级分页的方式是把所有页表项按1K为单位划分为若干个子表(最多1K个),用页目录表来记录每一个子表的位置。页目录表也是1K项长。页目录和页表的大小都是4K(每项4字节,1K项),恰好是一页大小。页目录的每一项记录一个页表的内存块号,通过页目录就可以找到各个页表。
![](https://i-blog.csdnimg.cn/blog_migrate/3b12d997aece89b50b93c8dbf78df771.png)
图5-18描述了二级地址变换的过程。进程的mm struct结构中记录了它的页目录地址,在进入CPU运行时,页目录地址被置入CPU的寄存器中,通过它即可找到进程的页目录。在地址变换时,首先用页目录号为索引查页目录,得到对应的页表的地址,再用页号为索引查页表,得到对应的内存块号,与页内位移合并即得到实际内存地址。
由于页目录表和页表存放在内存中,因此,要访问内存中的某一单元需要三次访问内存。为了加快访问速度,Linux将常用的页目录和页表的表项放在快表中。地址变换时首先访问快表,如果表项不在快表中,再去内存查找页目录表和页表。
![](https://i-blog.csdnimg.cn/blog_migrate/bd164ab4ce9e83bd7f07377ba6ce7f61.png)
6.4.4 空闲内存的管理
Linux系统用位示图和链表相结合的方法记录空闲内存的分布情况。系统定义了一个称为free_area的数组。对于i386平台的Linux系统来说,free_area数组的大小为10项,每项包含空闲链表的2个指针(next和prev)和位示图的1个指针(map)。它连接了10个空闲链表和位示图。通过它内核可以掌握系统中所有尺寸的连续的空闲空间的分布情况,作为分配内存的依据。图5-19所示是free_area数组的结构示意图。
![](https://i-blog.csdnimg.cn/blog_migrate/8de2f1754a1be476f108dd05029f890d.png)
若干个连续的页帧称为页块组(page block)。free_area数组用链表和位示图的方式记录了各种尺寸的空闲页块组的分布。
空闲链表是一个双向链表,数组第k项对应的链表记录系统中所有2k大小的空闲页块组的起始页帧号。例如,第0项描述所有单个空闲页帧,第1项描述所有2个页帧大的空闲页块组,第2项描述4个页帧大的空闲页块组。
如果将内存按相同大小的页块组划分并编号,则以偶数号开始的相邻的两个页块组称为伙伴(buddy)。如页块组大小为2帧,则0号页块组(0~1帧)与1号页块组(2~3帧)是伙伴,2号页块组(4~5帧)与3号页块组(6~7帧)是伙伴,但1号页块组与2号页块组不是伙伴。
位示图由若干个字节的二进制位构成,每位对应一对伙伴。数组第k项对应的位示图记录2k大小的一对伙伴的分配使用情况,为1表示对应的伙伴中有一个是空闲的页块,为0表示对应的两个伙伴页块都空闲或都不空闲。
在图5-19中,如果内存中页帧的使用情况如图右侧部分所示,则free_area[0]的空闲链表链接所有1帧长的空闲页块组,它们是第5、7、13等帧。它的位示图描述1帧长的伙伴的使用情况。前16帧的8对伙伴用8位描述,其中,5、7、13帧对应的伙伴位为1,其余为0。同理,free_area[1]的空闲链表链接所有2帧长的空闲页块组,分别是第10~11和14~15帧。它的位示图描述2帧长的伙伴的使用情况。前16帧的4对伙伴用4位描述,其中,10~11和14~15帧对应的伙伴位为1,其余为0。free_area[2]的空闲链表链接所有4帧长的空闲页块组,这里是0~3帧。它的位示图描述4帧长的伙伴的使用情况。前16帧的2对伙伴用2位描述,其中,0~3帧对应的伙伴位为1,其余为0。
6.4.5 内存的分配与回收
1. 伙伴分配算法
Linux采用伙伴(buddy)算法来分配和回收内存,内存的分配和回收的空间大小都是2k大小的页块组。对于i386系统,内存一次分配的最小尺寸是4 KB(20×4 KB),最大为2 MB(29×4 KB)。
当要分配内存时,先根据需要确定要分配的页块组大小。如需要n页,2k-1<n≤2k,则分配一个2k大小的页块组。分配时,在free_area[k]的链表中找一个空闲页块组,将其从链表中删除,返回首地址。若没有2k大小的页块组,就在free_area[k+1]的链表中取下一个,一分为二,分配一个,将另一个链入free_area[k]的链表中。如果没有2k+1大小的页块组就进一步地分裂更大的页块组。回收内存时,将释放的页块组链入适当的链表中。如果该页块组的伙伴为空闲(位示图的对应位为1),则将其与伙伴合并,加入到下一个数组项的链表中。如果还能合并就进一步合并下去。每次分配或回收操作后都要修改free_area数组的相应的链表和位示图。
Linux系统十分注重效率,buddy算法可以尽量减少内存碎片,增加连续内存分配成功的几率,使总体效率显著提高。但是这个算法可能造成空间的浪费,因为它每次分配的内存是2的整数幂个页,如果需要的内存量是33 KB,则实际分配的是16个连续的页帧(64 KB),将近50%的内存就浪费掉了。所以说,算法的高效率是通过牺牲内存资源利用率换来的。
2. 内存分配机制
Linux内核提供的最底层的内存分配函数是alloc_pages()。该函数使用buddy算法,每次分配2的整数幂个连续的页帧。分配成功后返回一个指针,指向第一个页帧的page结构。alloc_pages()函数的一个变种是_get_free_pages()函数,它的作用与alloc_pages()相同,只是返回的是第一个页的逻辑地址。与此两个函数对应的是free_pages(),用于释放页帧。
对于以字节为单位的分配来说,内核提供的函数是kmalloc()。Kmalloc()函数与C语言的malloc()族函数类似,用于获得以字节为单位的一块内存区。与alloc_pages()的不同之处在于,这个函数是按字节数分配一段足够大的内存区,返回它的首地址。Kmalloc()所分配的内存区在物理上是连续的。由于内核分配本质上是基于页的,所以真正分配的内存可能比请求的要多。与kmalloc()函数对应的是kfree(),用于释放内存区。
vmalloc()函数的工作方式类似于kmalloc(),只不过vmalloc()分配的内存的逻辑地址是连续的,而物理地址则无需连续。该函数通过建立页表将连续的逻辑地址映射到不连续的物理页帧上,所以在使用vmalloc()分配的内存时需要频繁查询页表,效率相对较低。与vmalloc()函数对应的释放函数是vfree()。
分配连续页帧的好处是构造页表的时候开销很低,同时访问起来效率也高。大多数情况下,硬件设备使用的内存区(如磁盘缓冲区等)必须是物理地址连续的页帧,因为硬件设备不理解虚拟地址。而软件使用的内存块(如进程的缓冲区)可以使用物理地址不连续的页帧,当然它的虚拟地址是连续的。尽管如此,很多内核代码都用kmalloc()来获得内存,而不是vmalloc(),这主要是出于性能的考虑。因此,vmalloc()仅在需要获得大块内存时才会使用。
alloc_pages()函数是按页分配的,即使是只需要小块内存也要分配整个页面。然而,内核和应用程序在运行过程中经常会重复地进行数据结构或对象的分配与释放。为这种小块内存而频繁地进行内存分配是对内存的极度浪费,并会导致内存碎片(难以找到大块连续的空闲内存)。
为满足小块内存的分配与释放,Linux提供了slab缓存机制,即slab分配器。它在页分配的基础上获取并建立内存缓存区域,管理对小块内存区的分配与释放请求。多数情况下,slab只需在slab缓存中分配和回收小内存区,一般无需调用内核的分配函数来实际地分配内存。slab分配器的优点显而易见,它提高了小内存分配与回收的效率,并避免了碎片问题。
6.4.6 页面的交换
Linux使用缺页中断处理和内存交换调度实现虚存。所有的页入和页出交换都是由内核透明地实现的。在建立进程时,整个进程映像并没有装入物理内存,而是链接到进程的虚拟地址空间中,进程只分配到少量内存块空间。运行中,系统为进程按需动态调页,进程所拥有的内存块数总是动态变化的。
由于页面交换会使程序的执行在时间上有较大的不确定性,故在实时系统中不宜采用页面交换机制。为此,Linux提供了系统调用swapon()和swapoff()来开启或关闭交换机制,默认是开启的。
1. 交换空间
从内存中换出的页面保存在外存中。Linux系统提供了两种形式的交换空间:一种是利用一个特殊格式的磁盘分区(Linux swap),称为交换区;另一种是利用标准文件系统中特殊的文件,这种文件具有固定的长度,称为交换文件。交换分区中同一个页面中数据块连续存放,因此读/写速度很快。交换文件的物理结构是索引方式的,数据可以分散地存放在磁盘的不连续块中,读/写起来速度较慢。所以交换分区的性能要比交换文件好得多。
有时,一个交换空间可能不够用,Linux系统可以同时管理多个交换空间,默认最大个数是8个。交换空间按照优先级排序,在实际使用过程中,通常以交换区为主,以交换文件为辅。系统安装时要设置适当的交换分区(约为实际内存的1.5倍),当需要更多交换空间时,临时增加几个交换文件,不需要时则撤销交换文件。
2. 页换入
进程内存页的换入是由缺页故障中断引起的。当进程要访问的线性地址所对应的页当前不在内存中时,硬件报出缺页中断。内核响应此中断,阻塞当前进程的运行,进入缺页中断处理程序运行。缺页中断的处理过程就是页换入的过程。
缺页中断处理程序根据CPU提供的缺页地址信息,找到虚拟存储区所对应的vm_area_struct结构,从磁盘中将所需的页装入,并更新页表。
由于Linux的页换出机制保证了内存中始终有一定量的空闲内存块,因此在缺页中断处理中不必考虑页面淘汰问题,从而加快了中断处理的速度。
3. 页换出
在Linux系统中,页面的换出工作由内核交换进程kswapd来完成。kswapd是一个具有高优先级的实时内核线程,它周期性地运行,在内存紧张时进行页面换出。这样就保证了系统中总是有足够的空闲块,使内存分配可以高效地运行。
Linux系统定义了两个数值pages_high和pages_low作为空闲块数量的上、下警戒值。内核监视系统中当前空闲内存的数量,当空闲块数量小于pages_low时,立即唤醒kswapd进行页面换出;当空闲块数量小于pages_high时,每次分配内存前都要唤醒kswapd, 进行页面换出;当空闲块的数量大于pages_high时,不做任何页面换出就可以立即进行内存分配。
交换进程kswapd依次通过三种途径换出内存中的页面:
(1) 缩减页面缓存(page cache)和缓冲区缓存(buffer cache)所占有的页帧。页面缓存是系统为了提高映像文件访问速度而设置的。缓冲区缓存是系统块设备使用的缓冲区,用来提高块设备的访问速度。在释放物理内存时,淘汰缓存页面是最简便的办法。
(2) 换出共享内存占用的页帧。如果第一步没有得到足够多的空闲块,就采取第二步措施,换出共享内存。共享页面由多个进程访问,因此在换出过程中必须依次对每一个进程的页表进行修改,这需要多次访问内存,增加了工作量。
(3) 换出其他进程占用的页帧。若上述两种措施仍然没有得到足够的空闲页帧,系统就要对所有进程进行扫描,寻找适合换出的候选进程,将其中部分页面丢弃或换出。与前两种途径相比,换出进程页面的效率最低。
Linux系统采用一种类似LRU的页面淘汰算法。系统根据页面的访问次数以及上次访问的时间来决定是否适合换出,优先换出那些很长时间没有被访问的页面。为此在描述页帧的page结构中设置有一个“age”字段,它的值随页面访问次数而增加,随着时间的流失而减小。当需要进行页面换出时,交换进程优先换出age值最小的页面。
习 题
5-1 存储管理的主要功能是什么?
5-2 什么是逻辑地址?什么是物理地址?为什么要进行地址变换?
5-3 静态地址变换与动态地址变换有什么区别?
5-4 简述页式分配思想和地址变换机制。
5-5 页式和段式内存管理有什么区别?
5-6 简述虚拟存储器的原理。虚拟存储器的容量受什么限制?
5-7 在页式存储系统中,若页面大小为2 KB,系统为某进程的0、1、2、3页面分配的物理块为分别5、10、4、7,求出逻辑地址5678对应的物理地址。
5-8 在页式存储系统中,如何实现存储保护和扩充?
5-9 什么是抖动?产生抖动的原因是什么?
5-10 Linux系统采用的存储管理方案是什么?
5-11 Linux系统的内存分配与回收采用什么算法?有什么特点?