计算机与操作系统:内存管理
内存概念
内存是计算机最重要的组件之一,它是程序与cpu沟通的桥梁,所有运行的程序都要加载到内存中执行。内存又被称为主存,其作用是存储cpu的运算数据,以及与硬盘等其他外部存储的交换数据。
内存内部由各种IC电路组成,它的种类很多,但主要分为以下三种类型:
- 随机存储器(RAM):可读可写,但是断电后,数据会消失。
- 只读存储器(ROM):可读不可写,断电后,数据不会消失。
- 高度缓存(Cache):它主要分为 L1(一级缓存),L2(二级缓存),L3(三级缓存),位于cpu与主存的中间,是比主存读写速度更快的存储器,它用来充当主存的缓存。
内存的IC电路虚拟结构:
- VCC和GND表示电源
- D0-D7表示数据信号(8bit=1byte)
- A0-A9表示地址信号(可以表示2^10=1024个地址),每一个地址存放1byte的数据,也就是上面的IC的容量是1KB。
- RD/WR分别是读/写控制信号。
内存管理
计算机初始阶段,只能运行一个程序,直接使用内存的物理地址读写数据。这种直接将物理地址暴露给程序的方式存在很大的问题。
- 程序随意的读写物理地址可能会破坏操作系统。
- 这种方式无法同时运行多个程序,多个程序间的地址空间无法界定,并且容易出现内存冲突及读写覆盖的问题。
动态重定位
针对以上问题当时的解决方案是使用动态重定位,将每个进程的地址空间映射到物理内存的不同部分,给每个Cpu配置两个特殊的硬件寄存器,分别是“基址寄存器”和“界限寄存器”,当一个程序运行时,将程序的起始地址保存在基址寄存器中,将程序的长度保存在界限寄存器中。 每次一个进程访问内存,读写数据时,cpu在将地址发给内存总线前,自动把基址值加到进程发的地址值上,同时检查程序提供的地址值是否大于界限寄存器里的值。如果地址超出了界限,就会产生错误并终止访问。
交换技术
如果内存地址存够大,可以容纳所有进程,那么以上方案是可行的,但现实中,往往所有程序的 RAM 总和远远大于内存空间。对于内存超载的问题,有两种通用的解决方案,最简单的策略是内存交换技术,即把一个进程完整的调入内存,运行一段时间后将它存回磁盘,等再次运行时再重新加载到内存。 第二种方案就是虚拟内存,该策略甚至能使程序在只有一部分被调入内存的情况下运行。
如下图,开始时内存中只有进程 A,后面又将进程 B,C 加载到内存(新创建或者是从磁盘交换至内存),当要将进程 D 加载到内存时,内存中的空间空间已不足以容纳 D 的地址空间,所以将进程 A 交换至磁盘。
经过多次交换,内存中会产生多个空闲区(内存空洞),通过将所有进程尽可能的向下移动,有可能将小的空闲区合并为一个大的空闲区,该技术称为内存紧缩。因为它要耗费大量的cpu时间,所以仅为必要时,执行该操作。
还有一个问题值得注意,当进程创建时,应该为它分配多大的内存,如果进程的大小是固定不变的,操作系统只需要按固定大小分配即可,可是随着程序的运行,占用内存的空间可能会持续增长,若进程与一个空闲区相邻,那么可以将空闲区分配给进程供其增长,而若进程与另一个进程相邻时,则要么将进程转移至一个足够大的空闲区,要么将其他进程交换至磁盘以产生足够大的空闲区。针对该问题,一种可行的方法时,在将进程交换至内存时,为它提前额外分配一些内存以备其后的增长,这种方式称为预分配。
空闲内存管理
在动态分配内存时,操作系统必须对其进行管理(记录使用区和空闲区)。一般而言,有两种方法跟踪内存的使用情况:位图和空闲区链表。
使用位图方法时,内存被划分为一个个相同的分配单元(几字节到几千字节),每个分配单元对应位图中的一位,0表示空闲,1表示占用。分配单元越小,位图越大,内存的利用率也越高。当要把一个要占用K个分配单元的程序加载至内存时,存储管理器必须搜索位图,在其中找到k个连续为0的串。
使用空闲区链表时,需要维护一个记录已分配内存段及空闲内存段的链表。其中链表的节点,包含以下几个字段:空闲区(H)和进程(P)的标志、起始地址、长度和指向下一个节点的指针(双向链表还需要 指向上一个节点的指针)。段链表是按照地址排序的,其好处是当进程终止或被换出时,链表节点的更新非常直接,如下图所示,X进程的终止,可能出现如下的四种更新结果。
当使用空闲区链表管理内存时,当需要将一个进程加载至内存时,也有多种策略分配内存地址: 首次适配算法(遍历链表,当遇到满足进程内存空间的空闲区时,即分配内存),最佳适配法(遍历整个链表,找出最小满足需求的空闲区,分配内存。该方法会产生很多小的空闲区无法利用,造成内存浪费)。最差适配法(总是分配最大的空闲区,是新的空闲区足够大从而可以继续使用,但是实际效果并不好)。还可以为进程和空闲区分别维护两个链表,这样可以增加分配内存的速度,但也无法避免的增加了复杂度。
虚拟内存
地址空间是对物理内存的抽象,而基址寄存器和界限寄存器又是对地址空间的抽象。随着计算机的普及与发展,单个程序的大小膨胀速度很快,甚至大到内存无法容纳的程度。其实并不需要将整个程序完全加载到内存中,只需要保证当前需要使用的部分在内存中即可。这时采用了一种虚拟内存的方法。
它的核心思想是:每个程序拥有自己的地址空间,这些空间又被分割为多个大小相同的单元,我们称这个单元为页,每个页都是一段连续的地址空间,这些页被映射到物理内存。并不是所有的页都在内存中才能运行程序,当 CPU 执行一个指令需要读写内存时,程序的虚拟地址不被直接送到内存总线,而是被送到内存管理单元(MMU),MMU 利用页表(虚拟地址与物理地址的映射关系表)寻找该地址空间的映射关系,如果页表中存在该映射关系(页表项中有一个 在/不在 位标识 该表项对应的虚拟页面在不在内存中),则直接映射为物理内存地址,如果不存在(即 在/不在 为 不在时),则由操作系统负责将缺失的部分装入物理内存并更新页表再重新执行失败的指令。
虚拟地址空间按照固定大小划分成被称为页的若干单元,在物理内存中对应的单元称为页框。页和页框通常的大小相同的。RAM和磁盘之间的交换也是以整个页面为单元进行的。
页面置换算法
当发生缺页中断时,操作系统必须将内存中的选择一个页面将其换出内存,以便为即将调入的页面腾出空间。如果要被换出的页面在内存驻留期间已经被修改过,就必须把它写回磁盘以更新该页面在磁盘上的副本;如果该页面没有被修改过,那么它在磁盘上的副本就已经是最新的,就不需要再回写,直接淘汰掉该页面即可。
当发生缺页中断时,可以随机选择一个页面来置换,但是选择一个不常被使用的页面置换,一定对性能有更好的提升。如果将一个被频繁使用的页面置换到磁盘,很可能短时间内它又被重新置换回内存,这就带来了不必要的开销。所以再页面置换选择上,也是一个值得优化的点。
分段式内存管理
如果说推动存储管理方式从固定分区到动态分区分配,进而又发展到分页存储管理方式的主要动力,是提高内存利用率,那么,引入分段存储管理方式的目的,则主要是为了满足用户(程序员)在编程和使用上多方面的要求,其中有些要求是其它几种存储管理方式所难以满足的。因此,这种存储管理方式已成为当今所有存储管理方式的基础
参考文档:基本分段存储管理方式
小结
我们看到在最简单的系统中是根本没有任何交换或分页的。一旦程序装入内存,它将持续在内存中运行,直到结束。一些操作系统一次只允许一个进程在内存中运行,而另一些操作系统支持多道程序设计。这种模型在小型或嵌入式实时系统中仍有用武之地。
接下来是交换技术。通过交换技术,系统可以同时运行总内存占用超过实际物理内存大小的多个进程。如果一个进程没有内存空间可用,它将会被交换到磁盘上。内存和磁盘上的空闲空间可以使用位图或空闲区链表来记录。
现代计算机都有某种形式的虚拟内存。最简单的情况下,每一个进程的地址空间被划分为同等大小的块,称为页面,页面可以被放入内存中任何可用的页框内。有多种页面置换算法,其中两个比较好的算法是:老化算法和工作集时钟算法。
为了使分页系统工作良好,仅选择算法是不够的,还要关注诸多问题,例如工作集的确定、内存分配策略以及所需页面大小等。
如果要处理在执行过程中大小有变化的数据结构,分段是一个有用的选择,它还能简化链接和共享。不仅如此,分段还有利于为不同的段提供不同的保护。有时,可以把分段和分页结合起来,以提供二维的虚拟内存。