操作系统内存管理
内存
内存被设计用来存储数据,以便程序在执行之前能够先被加载到内存中,进而被CPU高效地处理。这一机制有效地缓解了CPU与硬盘之间存在的速度差异和矛盾,确保了数据处理流程的顺畅进行。
一、内存管理
1. 进程运行的基本原理
在深入探讨内存管理的具体策略之前,我们首先需要深入理解进程运行的基本原理及其内在要求。
进程的创建始于程序与数据的内存装载过程,这一流程涉及将用户源代码转化为内存中可执行的程序,通常涵盖以下关键步骤:
- 编译过程。通过编译程序,用户的源代码被转化为多个目标模块,这些模块是程序执行的基础构件。
- 链接过程。链接程序负责将这些目标模块及其所需的库函数整合成一个完整的装入模块。这一步骤确保了程序在运行时能够访问所有必要的代码与数据。
- 装入过程。由装入程序负责将这一完整的装入模块加载至内存中,使其得以执行。
在程序的链接阶段,我们通常采用以下三种方式:
- 静态链接:即在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。
- 装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式,以提高装载效率。
- 运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。
2. 内存管理的概念
内存管理(Memory Management)构成了操作系统设计中的核心且最为错综复杂的组成部分之一。尽管计算机硬件技术日新月异,内存容量持续扩增,然而,面对所有用户进程及系统所需程序与数据的海量存储需求,将所有内容同时载入主存仍属不可能之事。因此,操作系统必须承担起对内存空间进行合理划分与高效动态分配的重任,这便是内存管理的精髓所在。
在多道程序设计的框架下,有效的内存管理显得尤为重要。它不仅能够极大地简化用户对存储器的使用流程,提升内存资源的利用率,还能借助虚拟技术,在逻辑层面上实现存储器的扩容。
- 逻辑地址(相对地址):是程序编写和编译时用的地址,基于某个基准。
- 物理地址(绝对地址):则是数据在内存中实际存储的位置。
内存管理的核心功能涵盖 内存空间的分配与回收、 地址转换机制、 内存空间的虚拟扩充、 存储保护机制 四个方面。
(1)地址转换机制
地址转换机制:在多道程序并行的环境中,程序中的逻辑地址与内存中的物理地址往往并不一致。因此,存储管理系统必须提供地址变换的功能,确保逻辑地址能够准确无误地映射到相应的物理地址上。
- 绝对装入方式:编译时若已知程序内存位置,则生成绝对地址代码,装入程序据此将程序数据精准装载至指定内存位置,但仅适用于单道程序环境。
- 静态重定位方式(可重定位装入):编译链接后,模块地址以0为起点编排,装入时根据内存情况灵活装载,并一次性将逻辑地址转换为物理地址。需一次性分配全部内存,作业运行期间不可移动或再申请内存。
- 动态重定位方式(动态运行时装入):同样以0为起点编排地址,但装入后不进行立即转换,而是在程序执行时动态进行。需重定位寄存器支持,允许程序内存移动,提供更高灵活性与效率。
(2)存储保护机制
存储保护机制:这一功能确保了各个作业能够在其专属的存储空间内独立运行,避免了相互之间的干扰与冲突。
内存保护可采取两种方法:
- 在CPU中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,CPU检查是否越界。
- 采用重定位寄存器(又称基址寄存器)和界地址寄存器(又称限长寄存器)进行越界检查。重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址
(3)内存空间的虚拟扩充
内存空间的虚拟扩充 :借助虚拟存储技术或自动覆盖技术等手段,我们可以在逻辑层面上实现内存的扩容,从而满足更大规模的程序运行需求。
内存空间的虚拟扩充主要依赖于覆盖技术和交换技术。
-
覆盖技术:
此技术将程序细分为多个段或模块,以优化内存使用。其中,常用的段被设定为常驻内存,确保它们在程序运行期间始终可用。而不常用的段则被灵活处理,仅在需要时被调入内存,使用完毕后即被调出,以释放内存空间供其他任务使用。为实现这一机制,内存被划分为一个“固定区”和若干个“覆盖区”。固定区用于存放那些需要常驻内存的段,而覆盖区则用于动态地调入和调出那些不常用的段。
-
交换技术:
当内存空间变得紧张时,系统采用交换技术来优化内存资源的分配。这一技术的核心思想是,在内存与磁盘之间动态地调度进程。具体来说,系统会将内存中某些当前不活跃或优先级较低的进程暂时换出到磁盘上,以释放内存空间。同时,系统也会检查磁盘上那些已具备运行条件且优先级较高的进程,将它们换入内存,以便它们能够继续执行。通过这种方式,系统能够灵活地管理内存资源,确保高效运行。
(4)内存空间的分配与回收
内存空间的分配与回收:操作系统负责主存储器空间的分配与管理工作,从而减轻了程序员在存储分配方面的负担,进一步提高了编程效率。
内存空间的分配包括连续分配方式和非连续分类方式。
- 连续分配:指为用户进程分配的必须是一个连续的内存空间。
- 非连续分类方式:为用户进程分配的可以是一些分散的内存空间。
3. 连续分配方式
(1)单一连续分配
单一连续分配:将内存明确分为系统区和用户区。系统区位于低地址,存储操作系统核心数据;用户区则存放用户进程及数据。此方式下,内存中仅运行一道用户程序,确保运行独立性和完整性。其优点为实现简单、无外部碎片,且可通过覆盖技术扩展内存。但仅适用于单用户、单任务环境,内部碎片问题导致存储器利用率低。
(2)固定分区分配
固定分区分配:为解决多道程序运行问题,将用户空间划分为多个固定或不等大小的分区,每个分区仅装入一道作业。操作系统通过分区说明表管理分区,记录大小、起始地址和状态。此方式实现简单、无外部碎片,但当程序过大时可能找不到合适分区,需采用覆盖技术,且内部碎片降低内存利用率。
(3)动态分区分配
动态分区分配:又称可变分区分配,此方式更为灵活,不预先划分内存分区,而是根据进程大小动态划分。系统分区大小和数量可变,更有效地利用内存资源,提高存储器利用率。
在动态分区分配方式下,若存在多个空闲分区均能满足内存分配需求时,就需要借助动态分区算法来确定最合适的分区进行分配。
- 首次适应算法(First Fit):每次都从低地址开始查找,找到第一个能满足大小的空闲分区。此算法综合看性能最好,算法开销小 。
- 最佳适应算法(Best Fit):优先使用更小的空闲区,从而尽可能多地留下大片的空闲区。此算法可以保证当“大进程”到来时能有连续的大片空间。
- 最坏适应算法(Worst Fit):又称 最大适应算法(Largest Fit),在每次分配时优先使用最大的连续空闲区,从而防止分配后剩余的空闲区就太小。此算法可以尽可能少地留下太多难以利用的小碎片。
- 邻近适应算法(Next Fit):每次都从上次查找结束的位置开始检索,从而减小查找的开销。此算法开销较小并避免低地址部分出现很多小空闲分区。
4. 非连续分配方式
(1)基本分页存储管理(Paging)
固定分区会产生内部碎片,而动态分区会产生外部碎片,这两种技术对内存的利用率都不高。为了尽量避免碎片的产生,我们引入了分页的方法。
**分页(Paging)**是一种内存管理技术,通过将内存和进程的逻辑地址空间划分为大小相等的固定块来提高内存利用率,减少碎片的产生。
通过分页管理,操作系统可以将进程的逻辑地址映射到物理内存中的页框,从而提高内存利用率,减少碎片问题,并实现高效的内存管理。
分页管理的基本概念:
进程的逻辑地址空间被划分为若干大小相等的部分,每个部分称为页(Page)。页是逻辑地址空间的基本单位。页在进程逻辑地址空间中的编号被称为页号(Page Number),从0开始。
内存空间被划分为若干大小相等的分区,每个分区称为页框或页帧(Page Frame)。页框是物理内存的基本单位。页框在物理内存中的编号被称为页框号(Page Frame Number),从0开始。
操作系统为每个进程维护一张页表(Page Table),用于记录进程的每个页在物理内存中的存放位置。页表通常存储在进程控制块(PCB)中。每个进程对应一张页表。
内部碎片与内存交换策略
内存分页机制下,内存分配的最小单位是页,这意味着即使程序所需内存小于一页,也至少要分配一个完整的页,从而导致页内可能存在内存浪费现象,这被称为内部内存碎片。
当系统内存空间不足时,操作系统会智能地管理资源,通过释放那些最近未被使用的内存页面来腾出空间,即将其暂时写入硬盘,这一过程称为换出(Swap Out)。当这些页面再次被需要时,它们会被重新加载回内存这一过程称为称为换入(Swap In)。由于每次交换通常仅涉及少数一个或几个页面,因此写入磁盘的时间相对较短,从而确保了内存交换操作具有较高的效率。
基本地址变换机构
基本地址变换机构可以借助进程的页表将逻辑地址转换为物理地址。
当CPU需要访问一个虚拟地址时,它首先根据页号找到页表中的对应条目,然后从该条目中读取物理页框号。最后将物理页框号与页内偏移组合起来,形成物理地址。
然而,页表具有空间上的缺陷。操作系统能同时运行大量进程,这导致页表规模庞大。在32位系统中,若页面为4KB,则4GB虚拟地址空间需约100万个页表项,每项4字节,整个页表即占4MB。但每个进程均有独立虚拟地址空间及页表,因此100个进程便需400MB内存来存储页表,内存开销显著。到了64位环境,这一开销更是剧增。
为了解决这一问题,提出了二级页表。
二级页表通过两个层次的表结构来映射虚拟地址到物理地址。这两个层次分别是页目录和页表。页目录中的每一项都指向一个页表,而页表中的每一项则指向一个物理页帧。
(2)基本分段存储管理(Segmentation)
程序由多个逻辑部分构成,这些部分包括代码段、数据段、栈段和堆段等。鉴于各段具有不同的属性和功能,采用**分段(Segmentation)**技术将这些部分有效地区分开来,以确保程序的结构清晰且运行高效。
在分段机制下,虚拟地址到物理地址的映射过程涉及几个关键组件和步骤。
分段系统的逻辑地址结构由段选择因子和段内地址(段内偏移量)所组成。
- 段选择因子:通常保存在段寄存器中,它包含了段号,这个段号用作段表的索引来查找对应的段表,段号的位数决定了每个进程最多可以分几个段。
- 段内偏移量:在一个逻辑段内部,从段的起始位置到目标数据或指令位置之间的相对距离或偏移量。段内地址位数决定了每个段的最大长度是多少。
程序分多个段,各段离散地装入内存,为了保证程序能正常运行,就必须能从物理内存中找到各个逻辑段的存放位置。为此,需为每个进程建立一张段映射表,简称段表。每个段对应一个段表,其中记录了该段在内存中的起始位置(又称基址)、段的界限和特权等级。各个段表项的长度是相同的。
虚拟地址到物理地址的映射过程如下:
当CPU要访问一个虚拟地址时,它首先会根据段选择子在段表中找到对应的段的基地址,然后将段内偏移量与基地址相加,从而得到物理内存中的实际地址;如果段内偏移量是合法的(即位于0和段界限之间),则映射成功,CPU可以访问到相应的物理内存地址。
内存碎片:
内存分段管理是一种按需为程序分配内存空间的方法,其中内存碎片主要分为内部碎片和外部碎片。内部碎片因固定大小分配单元与实际需求不匹配而产生,在分段管理中通过按需分配段来避免;
外部碎片则由多个不连续小内存块导致新程序无法装载,可通过内存交换等技术解决。
当内存不足时,系统会将部分暂时不需要的内存数据写入硬盘上,从而腾出更多的内存空间供其他程序使用。当需要访问被交换出去的数据时,系统会将其重新调入内存。重新调入内存后,不会存到原来的位置,而是存到其他内存数据的旁边,以达到将内存碎片合并的目的。
(3)段页式存储管理
分段与分页的区别:
- 页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
- 页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序。
- 分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。
方式 | 优点 | 缺点 |
---|---|---|
分页管理 | 内存空间利用率高,不会产生外部碎片,只会有少量的页内碎片 | 不方便按照逻辑模块实现信息的共享和保护 |
分段管理 | 很方便按照逻辑模块实现信息的共享和保护 | 如果段长过大,为其分配很大的连续空间会很不方便;会产生外部碎片 |
所以可将内存分段和内存分页组合起来在同一个系统中使用,通常称为段页式内存管理。
段页式内存管理:将进程按逻辑模块分段,接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页。
用于段页式地址变换的数据结构是每一个程序一张段表,每个段又建立一张页表,段表中的地址是
页表的起始地址,而页表中的地址则为某页的物理页号,如图所示。
段页式地址变换中要得到物理地址须经过三次内存访问: 第一次访问段表,得到页表起始地址; 第二次访问页表,得到物理页号; 第三次将物理页号与页内位移组合,得到物理地址。
二、虚拟内存
1. 虚拟内存
(1)传统存储方式管理的特点与缺点
- 一次性:作业必须一次性全部装入内存后才能开始运行。这会造成两个问题
- 作业很大时,不能全部装入内存,导致大作业无法运行;
- 当大量作业要求运行时,由于内存无法容纳所有作业,因此只有少量作业能运行,导致多道程序并发度下降。
- 驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行,这就导致了内存中会驻留大量的、暂时用不到的数据,浪费了宝贵的内存资源。
(2)局部性原理
- 时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的)
(3)虚拟内存(Virtual Memory)
基于局部性原理,在程序装入时,系统仅将程序的一部分载入内存,而将其余部分留在外存,从而启动程序的执行。
具体过程如下:
- 在程序运行过程中,当所需的信息不在内存中时,操作系统会将所需的部分调入内存,然后继续执行程序,此过程被称为请求调页功能;
- 操作系统会将内存中暂时不使用的内容换出到外存,以腾出空间存放即将调入内存的信息,此过程被称为页面置换功能。
在操作系统的这种管理方式下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存(Virtual Memory),又被称为虚拟存储器。
虚拟内存之所以得名,是因为这个内存实际上并不存在。它通过系统提供的部分装入、请求调入和置换功能(对用户完全透明),使用户感觉仿佛存在一个比实际物理内存大得多的存储器。虚拟存储器的大小由计算机的地址结构决定,并不是简单地将内存和外存的容量相加。
虚拟内存有一下三个主要特征:
- 多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。
- 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
- 虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。
(4)虚拟内存的实现
虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会不方便实现。因此,虚拟内存的实现需要建立在离散分配的内存管理方式基础上。
常用的方式有:请求分页存储管理、请求分段存储管理、请求段页式存储管理。
2. 请求分页管理方式
前面已经介绍过基本分页存储管理
请求分页存储管理与基本分页存储管理的主要区别:
- 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
- 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
(1)请求分页管理的页表
与基本分页管理相比,请求分页管理的页表中,需要保存更多信息:
- 为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存中存放的位置。
- 当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面;有的页面没有被修改过,就不用再浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。
(2)缺页中断机构
在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项;如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断;一条指令在执行期间,可能产生多次缺页中断。
(3)地址变换机构
由请求分页管理与基本分页管理的区别可以看出,请求分页管理需要在基本分页管理的基础上新增以下几个步骤:
- 请求调页(查到页表项时进行判断)
- 页面置换(需要调入页面,但没有空闲内存块时进行)
- 需要修改请求页表中新增的表项