内存管理
内存RAM是计算机中一种需要认真管理的重要资源,将讨论操作系统是怎样对存储器创建抽象模型以及如何管理它们的。
分层存储器体系(memory hierarchy),即在这个体系中,计算机有若干兆(MB)快速、昂贵且易失性的高速缓存(cache),数千兆(GB)速度与价格适中且同样易失性的内存,以及几兆兆(TB)低速、廉价、非易失性的磁盘存储,另外还有诸如DVD和USB等可移动存储装置。
操作系统的工作是将这个存储体系抽象为一个有用的模型并管理这个抽象模型。
操作系统中管理分层存储器体系的部分称为存储管理器(memory manager)。它的任务是有效地管理内存,即记录哪些内存是正在使用的,哪些内存是空闲的:在进程需要时为其分配内存,在进程使月完后释放内存。
3.1 无存储器抽象
直接访问物理内存
不可能同时运行两个程序
即使存储器模型就是物理内存,还是存在一些可行选项
在不使用存储器抽象的情况下运行多个程序
1、使用交换概念
2、特殊硬件 + 保护键 + PSW(Pprogram Status Word,程序状态字)
问题分析和解决方法(静态重定位)
3.2 一种存储器抽象:地址空间
把物理地址暴露给进程会带来下面几个严重问题。
问题说明:
第一,如果用户程序可以寻址内存的每个字节,它们就可以很容易地(故意地或偶然地)破坏操作系统,从而使系统慢慢地停止运行(除非使用特殊的硬件进行保护,如IBM 360的锁键模式)。即使在只有一个用户进程运行的情况下,这个问题也是存在的。
第二,使用这种模型,想要同时运行(如果只有一个CPU就轮流执行)多个程序是很困难的。在个人计算机上,同时打开几个程序是很常见的(一个文字处理器,一个邮件程序,一个网络浏览器),其中一个当前正在工作,其余的在按下鼠标的时候才会被激活。在系统中没有对物理内存的抽象的情况下,很难实现上述情景,因此,我们需要其他办法。
3.2.1 地址空间的概念
要使多个应用程序同时处于内存中并且不互相影响,需要解决两个问题:保护和重定位
新的存储器抽象:地址空间
地址空间是一个进程可用于寻址内存的一套地址集合,每个进程都有一个自己的地址空间,并且这个地址空间独立于其他进程的地址空间(除了一些特殊情况下进程需要共享它们的地址空间外)
如何给每个程序一个自己独有的地址空间?
讨论一个简单方法:基址寄存器与界限寄存器
这个简单的解决办法是使用动态重定位,简单地把每个进程的地址空间映射到物理内存的不同部分。
从CDC 6600(世界上最早的超级计算机)到Intel 8088(原始IBM PC的心脏),所使用的经典办法是给每个CPU配置两个特殊硬件寄存器,通常叫作基址寄存器和界限寄存器,当使用基址寄存器和界限寄存器时,程序装载到内存中连续的空闲位置且装载期间无须重定位。
当一个进程运行时,程序的起始物理地址装载到基址寄存器中,程序的长度装载到界限寄存器中。在下图中,当第一个程序运行时,装载到这些硬件寄存器中的基址和界限值分别是0和16 384。当第二个程序运行时,这些值分别是16384和32 768。如果第三个16KB的程序被直接装载在第二个程序的地址之上并且运行,这时基址寄存器和界限寄存器里的值会是32768和16384。
每次一个进程访问内存,取一条指令,读或写一个数据字,CPU硬件会在把地址发送到内存总线前,自动把基址值加到进程发出的地址值上。同时,它检查程序提供的地址是否等于或大于界限寄存器里的值。如果访问的地址超过了界限,会产生错误并中止访问。
使用基址寄存器和界限寄存器是给每个进程提供私有地址空间的非常容易的方法,因为每个内存地址在送到内存之前,都会自动先加上基址寄存器的内容。
在很多实际系统中,对基址寄存器和界限寄存器会以一定的方式加以保护,使得只有操作系统可以修改它们。在CDC 6600中就提供了对这些寄存器的保护,但在Intel 8088中则没有,甚至没有界限寄存器。但是,Intel 8088提供了多个基址寄存器,使程序的代码和数据可以被独立地重定位,但是没有提供引用地址越界的预防机制。
使用该方法的缺点是:每次访问内存都需要进行加法和比较运算,比较运算可以做得很快,但是加法运算由于进位传递时间的问题,在没有使用特殊电路的情况下会显得很慢。
3.2.2 交换技术
如果计算机物理内存足够大,可以保存所有进程,那么之前提及的所有方案都或多或少是可行的。但实际上,所有进程所需的RAM数量总和通常要远远超出存储器能够支持的范围。
在一个典型的Windows,OS X或Linux系统中,在计算机完成引导后会启动50~100个甚至更多的进程。
例如,当一个Windows应用程序安装后,通常会发出一系列命令,使得在此后的系统引导中会启动一个仅仅用于查看该应用程序更新的进程。这样一个进程会轻易地占据5~10MB的内存。
其他后台进程还会查看所收到的邮件和进来的网络连接,以及其他很多诸如此类的任务。并且,这一切都发生在第一个用户程序启动之前。当前重要的应用程序如Photoshop一启动就轻易地占据500MB内存,而开始处理数据后可能需要数千兆字节(GB)的空间。
因此,把所有进程一直保存在内存中需要巨大的内存,如果内存不够,就做不到这一点。
有两种处理内存超载的通用方法。
最简单的策略是交换(swapping)技术,即把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘。空闲进程主要存储在磁盘上,所以当它们不运行时就不会占用内存(尽管其中的一些进程会周期性地被唤醒以完成相关工作,然后就又进入睡眠状态)。
另一种策略是虚拟内存(virtual memory),该策略甚至能使程序在只有一部分被调入内存的情况下运行。
交换技术流程说明
问题描述:
1、空闲区
交换在内存中产生了多个空闲区(hole,也称为空洞),通过把所有的进程尽可能向下移动,
有可能将这些小的空闲区合成一大块。该技术称为内存紧缩(memory compaction)。
通常不进行这个操作,因为它要耗费大量的CPU时间。
例如,一台有16GB内存的计算机可以每8ns复制8个字节,它紧缩全部内存大约要花费16s。
2、进程如何分配内存
有一个问题值得注意,即当进程被创建或换入时应该为它分配多大的内存。
若进程创建时其大小是固定的并且不再改变,则分配很简单,
操作系统准确地按其需要的大小进行分配,不多也不少。
但是如果进程的数据段可以增长,例如,很多程序设计语言都允许从堆中动态地分配内存,
那么当进程空间试图增长时,就会出现问题。
若进程与一个空闲区相邻,那么可把该空闲区分配给进程供其增大。
另一方面,若进程相邻的是另一个进程,那么要么把需要增长的进程移到内存中一个足够大的区域中去,
要么把一个或多个进程交换出去,以便生成一个足够大的空闲区。
若一个进程在内存中不能增长,而且磁盘上的交换区也已满了,
那么这个进程只有挂起直到一些空间空闲(或者可以结束该进程)。
针对动态增长的进程内存分配方法
如果大部分进程在运行时都要增长,为了减少因内存区域不够而引起的进程交换和移动所产生的开销,
一种可用的方法是,当换入或移动进程时为它分配一些额外的内存。
然而,当进程被换出到磁盘上时,应该只交换进程实际上使用的内存中的内容,
将额外的内存交换出去是一种浪费。
3.2.3 内存空闲管理
动态分配内存时,操作系统必须对其进行管理。
一般而言,有两种方法跟踪内存使用情况:位图和空闲区链表
1、使用位图的存储管理
位图的重要设计因素和缺点
2、使用链表的存储管理
分配内存算法(在空闲链表情况下)
3.3 虚拟内存
虚拟内存((virtual memory)
基本思想是:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称作一页或页面((page)。
每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。
当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。
当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。
从某个角度来讲,虚拟内存是对基址寄存器和界限寄存器的一种综合。8088为正文和数据分离出专门的基址寄存器(但不包括界限寄存器)。而虚拟内存使得整个地址空间可以用相对较小的单元映射到物理内存,而不是为正文段和数据段分别进行重定位。
虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。
3.3.1 分页
虚拟地址映射工作流程
页面和页框
问题描述:
通过恰当地设置MMU,可以把16个虚拟页面映射到8个页框中的任何一个。
但是这并没有解决虚拟地址空间比物理内存大的问题。
在图3-9中只有8个物理页框,于是只有8个虚拟页面被映射到了物理内存中,
在图3-9中用叉号表示的其他页面并没有被映射。在实际的硬件中,用一个“在/不在”位(present/absent bit)记录页面在内存中的实际存在情况。
缺页中断或缺页错误
MMU内部结构及如何工作
虚拟地址被分为页号和偏移量
页号作为页表的索引,以得出对应于该虚拟页面的页框号
页框号+偏移量 = 物理地址
3.3.2 页表
页表项结构
3.3.3 加速分页过程
问题描述:
1、虚拟地址到物理地址的映射必须非常快
2、如果虚拟地址空间很大,页表也会很大
1、转换检测缓冲区(Translation Lookaside Buffer, TLB)
工作流程
2、软件TLB管理
3.3.4 针对大内存的页表
如何解决巨大的虚拟地址空间
1、多级页表
工作流程
拓展
2、倒排页表
3.4 页面置换算法
当发生缺页中断时,操作系统必须在内存中选择一个页面将其换出内存,以便为即将调入的页面腾出空间。
如果要换出的页面在内存驻留期间已经被修改过,就必须把它写回磁盘以更新该页面在磁盘上的副本,
如果该页面没有被修改过(如一个包含程序正文的页面),那么它在磁盘上的副本已经是最新的,不需要回写。直接用调入的页面覆盖被淘汰的页面就可以了。
当发生缺页中断时,虽然可以随机地选择一个页面来置换,但是如果每次都选择不常使用的页面会提升系统的性能。
如果一个被频繁使用的页面被置换出内存,很可能它在很短时间内又要被调入内存,这会带来不必要的开销。
3.4.1 最佳页面置换算法
3.4.2 最近未使用页面置换算法
3.4.3 先进先出页面置换算法
3.4.4 第二次机会页面置换算法
3.4.5 时钟页面置换算法
3.4.6 最近最少使用页面算法
用软件模拟LRU
老化算法
3.4.8 工作集页面置换算法
3.4.9 工作集时钟页面置换算法
3.4.10 页面置换算法小结