我们都知道,计算机工作的过程概括起来就是 CPU 去内存中读取指令并执行的过程,但是如果运行我们的程序直接操作物理内存,将会引发很多的问题(比如不同进程之间访问/修改的隔离、权限等等),所以爱操心的操作系统就帮我们实现了内存管理。
暂时可以先简单地理解为操作系统通过虚拟内存技术实现了内存管理,首先抽象出一层虚拟的内存空间,当进程创建时,给每个进程都分配一个虚拟的内存空间,这样程序所使用的地址就是虚拟内存地址,然后实际运行时,再把虚拟地址映射到物理地址,从而给程序分配物理内存地址使用。
这样,在编写程序的时候就可以尽情地使用虚拟地址了,其他的一系列工作都交由操作系统帮我们完成。
操作系统的内存管理需要实现以下几个功能:
本文将从分配物理内存开始介绍:
内存分配指的是操作系统将物理内存分配给程序以使用。(和编程语言的分配内存不同,如 C 中的 malloc
、mmap
等,后续会慢慢讲到的)
分配内存经历了两个发展阶段,一开始是使用连续型分配的方式,后面慢慢发展到了非连续型分配,现代的操作系统基本上都是使用非连续型分配的方式。这里的连续和非连续,指的是分配给程序的物理内存空间是否连续。
连续分配方式是最早出现的一种存储器分配方式,分配的策略为一个用户程序分配一个连续的物理内存空间。
缺点:
连续型分配有以下四种方式:单一连续分配、固定分区分配、动态分区分配、动态重定位分配。
内存碎片指的是不可用的空闲内存。外部内存碎片指的是未被分配,但是由于太小而无法被分配的内存空间。内部碎片指的是已经被分配,但是未被进程使用的内存空间。
先把内存简单理解为一个大数组,假设把 0~50 分配给了进程A,把 55~100 分配给了进程B,那么 50~55 就是外部碎片。假如进程A实际只用了 40 的空间,那还有 10 就是内部碎片。
单道程序环境下经常使用的分配方式,将整个内存直接交由一个程序独占,当其他程序需要使用的时候,需要覆盖(Overlay)整个内存,即一次只能运行一个程序。
优点是简单高效,无外部碎片。缺点是可能存在大量内部碎片,内存利用率低,且只能用于单用户。
是最简单的多道程序存储管理方式,将用户内存空间划分为若干个固定大小(不同分区大小可以不相等),每个分区只装入一道作业。
为便于内存分配,通常将分区按大小排队,并为之建立一张分区使用表,其中各表项包括每个分区的起始地址、大小及状态。
这种方式不会产生外部碎片,但是内部碎片可能较大,也有可能因为程序太大而无法放入任何一个分区。
动态分区分配又称可变分区分配,不预先划分内存,而是在程序装入内存时,根据进程的大小动态地建立分区,并使得分区的大小正好适合进程的需要,因此系统中分区的大小和数目是可变的。
为了实现动态分区分配,通常有两种方式:空闲分区表和空闲分区链。
这种方式在开始时很好,但是随着时间的推移,内存中会产生越来越多的碎片。
可以通过紧凑技术减少外部碎片。通过移动内存中作业的位置,把原来多个分散的小分区拼接成一个大分区的方法。
系统每紧凑一次,就要对移动了的程序或数据的地址进行修改,这个功能不仅实现复杂,而且还大大地影响到系统的效率。
紧凑分为两种,其中静态重定位是通过软件的方式实现的,也是动态分区分配中所使用的;而动态重定位是结合硬件支持的,是动态重定位分配中所使用的。
把一个新作业装入内存,需要按照一定的分配算法选出一个分区分配给作业。常见的动态分区分配算法有以下四种:
FF,首次适应算法,First Fit。从空闲分区表的第一个表目起查找该表,把最先能够满足要求的空闲区分配给作业。
NF,循环首次适应算法,Next Fit。从上次找到的空闲分区的下一个空闲分区开始查找,直至找到一个能满足要求的空闲分区。
BF,最佳适应算法,Best Fit。从全部空闲区中找出能满足作业要求的、且大小最小的空闲分区。最小化外部碎片的产生。
WF,最差适应算法,Worst Fit。从全部空闲区中找出能满足作业要求的、且大小最大的空闲分区。避免产生太多微小碎片。
除了这四种最常用的,还有其他的分配算法:
QF,快速适应算法,Quick Fit。将空闲分区按照大小进行分类,并使用链表将同一大小的分区进行连接,然后使用索引表进行管理。
伙伴系统,buddy system。使用二进制优化的思想,将内存以2的幂为单位进行分配,合并时只能合并是伙伴的内存块。两个内存块是伙伴的三个条件是:
不同的适应算法在不同的情况下各有优劣。
动态重定位分配,是在动态分区分配的基础上,加上硬件的支持,从而实现动态重定位的分配方式。
单纯用软件实现紧凑的效率非常低,需要频繁地移动内存位置,且只有在程序未占用 CPU 时才能移动。
除了软件的方式外,还可以用硬件协助实现。通过在系统中增设一个重定位寄存器,用它来存放程序在内存中的起始地址。程序在执行时,真正访问的内存地址是相对地址与重定位寄存器中的地址相加而形成的。当系统对内存进行了紧凑时,不需对程序做任何修改,只要用该程序在内存的新起始地址去置换原来的起始地址即可。
非连续型分配有分段和分页两种。非连续分配允许一个程序分散地装入到不相邻的物理内存分区中,根据分区的大小是否固定分为分页存储管理方式和分段存储管理方式。非连续型分配是现代操作系统常用的分配方式,比如 Linux 就是结合两种方式的段页式管理。
优点:
缺点:
针对分段和分页,后续文章将作详细介绍。
内存分配主要有连续型分配和非连续型分配两种,其中现代操作系统使用的通常为非连续型分配。
连续型分配主要有四种方式,分别为单一连续分配、固定分区分配、动态分区分配、动态重定位分配。
答:操作系统实际分配给程序的物理内存不一定是连续的,但是程序所使用的虚拟内存是连续的。因为根据 CPU 的工作原理,CPU 需要从 PC 指针(Program Counter,程序计数器,指向下一条指令的地址)读取指令、到执行、再到下一条指令,不断循环。由于 PC 指针是顺序地指向下一个虚拟地址,所以程序所使用地虚拟内存需要是连续的。
答:可以。由于操作系统的内存管理,分配给每个进程的虚拟内存都是独立且隔离的。