文章目录

  • RAM是计算机中需要认真管理的重要资源
  • 目前普通家用计算机的存储容量已是
    • 60早期全球最大的IBM 7094的10000倍以上,
    • 但程序大小的增长比内存容量的增长速度要快得多
  • 如帕金森定律:不管存储器多大,程序都可填满它
  • 这章讨论OS是怎样对存储器创建抽象模型
    • 及怎样管理它们

都想有存储器:私有、容量大、速度快,永久(断电时不丢数)

  • 当我们期望这样的存储器时,何不进一步要求它价格低廉呢?
  • 目前的技术不能为我们提供这样的存储器。
  • 也许你会有解决方案

除此之外的选择

  • 人们提出了分层存储器体系( memory hierarchy)的概念,
  • 此体系中,计算机有若干(MB)快速、昂贵且易失性的高速缓存( cache),
    • 数(GB)速度与价格适中且同样易失性的内存
    • 几(TB)低速、廉价、非易失性的磁盘存储
    • 还有诸如DVD和USB等可移动存储装置
  • OS的工作是将这个存储体系抽象为一个有用的模型并管理这个抽象模型。

OS中管理分层存储器体系的部分称为memory manager

  • 任务是有效管理内存,记录哪些内存是正用,哪些是空闲
    • 在进程需要时为其分内存,在进程使用完后释放

本章研究几个不同的存储管理方案,从简单到复杂的方案

  • 最底层的高速缓存的管理由硬件来完成,
    • 本章介绍针对编程人员的内存模型,
    • 及怎样优化管理内存
  • 至于永久性存储器————磁盘————的抽象和管理,是下章主题
  • 从最简单的管理方案讨论,逐步深入到更为缜密的方案

3.1 无存储器抽象

最简单的存储器抽象就是无抽象

  • 早期大型计算机(60年代前)、小型计算机(270年代前)和个人计算机(280年代前)都无存储器抽象。
    • 毎个程序都直接访问物理内存。
    • 当一个程序执行如下指令:
    • MOV REGISTER1, 1000
    • 计算机会将位置为1000的物理内存中的内容移到 REGISTER1中。
  • 因此,那时呈现给编程人员的存储器模型就是简单的物理内存:从0到某个上限的地址集合,
    • 每个地址对应一个可容纳一定数目二进制位的存储单元,通常是8个。

这种情况下,想在内存中同时运行两个程序是不可能

  • 如果第一个程序在2000的位置写入个新的值,将会擦掉第二个程序存放在相同位置上的所有内容,所以同时运行两个程序是根本行不通的,
    这两个程序会立刻崩溃。

不过即使存储器模型就是物理内存,还是存在一些可行选项的。

  • 图3-1展示了三种变体。

3内存管理

  • 3-1a,OS于RAM(随机访问存储器)底部
  • 3-1b,OS位于内存顶端的ROM(只读存储器)中
  • 3-1c,设备駆动程序位于内存顶端的ROM中,而OS的其他部分位于下面的RAM的底部
  • 第一种方案以前被用在大型机和小型计算机上,现在很少使用了。
  • 第二种方案被用在一些掌上电脑和嵌入式系统中。
  • 第三种方案用于早期的个人计算机中(例如运行MS-DOS的计算机),
    • 在ROM中的系统部分称为BIOS( Basic Input Output System,基本输入输出系统)。
  • 第一种方案和第三种方案的缺点是用户程序出现的错误可能摧毁操作系统,引发灾难性后果。

当按这种方式组织系统时,通常同一个时刻只能有一个进程在运行。

  • 一且用户键入了一个命令,OS就把需要的程序从磁盘复制到内存中并执行;当进程运行结東后,操作系统在用户终端显示提示
    符并等待新的命令。收到新的命令后,它把新的程序装入内存,覆盖前一个程序。

在没有存储器抽象的系统中实现并行的一种方法是使用多线程来编程。

  • 由于在引入线程时就假设一个进程中的所有线程对同一内存映像都可见,那么实现并行也就不是问题了。
  • 虽然这个想法行得通,但却没有被广泛使用,因为人们通常希望能够在同一时间运行没有关联的程序,而这正是线程抽象所不能
    提供的。
  • 更进一步地,一个没有存储器抽象的系统也不大可能具有线程抽象的功能。

不用存储器抽象的情况下运行多个程序

  • 即使无存储器抽象,同时运行多个程序也可能
  • OS只需要把当前内存中所有内容保存到磁盘文件中,然后把下个程序读入到内存中再运行即可
  • 只要在某一个时间内存中只有一个程序,那么就不会发生冲突。
  • 这样的交换概念会在下面讨论。

在硬件的帮助下,即使无交换功能,并发运行多个程序也可能

  • IBM360的早期模型是这样解决的:
    • 内存被划为2KB的块,
    • 每个块被分配一个4位的保护键,
    • 保护键存储在CPU的特殊寄存器中
  • 一个内存为1MB的机器只需要512个这样的4位寄存器,总共256字节
  • PSW( Program Status Word,程序状态字)中存有一个4位码。
  • 一个运行中的进程如果访问保护键与其PSW码不同的内存,360的硬件会捕获到这一事件。
  • 只有OS可以修改保护键,
    • 防止用户进程之间、用户进程和OS之间的干扰

但这决方法有一个重要缺陷

  • 如图3-2所示,有两个程序,各为16KB,如图3-2a和图3-2b所示。

3内存管理

  • 前者加了阴影表示它和后者用不同的内存键。
  • 第一个程序一开始就跳转到地址24,是一条MOV指令
  • 第二个程序一开始跳转到地址28,是一条CMP指令。
  • 与讨论无关的指令没画出来
  • 当两个程序被连续地装载到内存中从0开始的地址时,内存中的状态就如同图3-2c所示。
  • 这个例子里,假设OS是在高地址处,图中没画

程序装载完毕后就可以运行了。

  • 由于它们的内存键不同,它们不会破坏对方的内存。
  • 但在另一方面会发生问题
  • 当第一个程序运行时,它执行了JMP24,然后不出预料地跳转到了相应的指令
  • 这个程序会正常运行

但当第一个已经运行一段时间后,OS可能会决定运行装载在第一个程序之上的地址16384处的程序

  • 第一条指令是JMP 28,会使程序跳到第一个程序的ADD指令,而不是事先设定的CMP
  • 由于对内存地址的不正确访问,这个程序很可能在1秒之内就崩溃

问题在于两程序都用了绝对物理地址,这要避免

  • 希望每个程序 用 一套私有的本地地址来进行内存寻址
  • 下面展示这种技术
  • IBM360的补救方案是在第二个程序装载到内存的时候,用静态重定位的技术修改它
  • 工作方式如下:
    • 当程序被装载到16384时,常数16384被加到每一个程序地址上
    • 虽然这个机制在不出错误的情况下是可行的,但不是通用解法,同时会减慢装载速度
  • 且它要求给所有的可执行程序提供额外的信息来区分哪些内存字中存有(可重定位的)地址,哪些没有。
  • 毕竟,图3-2b中的“28”需要被重定位,
  • 但像 MOV REGISTER1, 28
    • 把数28送到 REGISTER1的指令不可重定位
    • 装载器要一定的方法来辨别地址和常数

最后,如1章指出的,计算机世界的发展总是倾向于重复历史

  • 直接用物址对大型、小型、台式和笔记本来说已成久远的记忆
    • 但缺少存储器抽象的情况
    • 在嵌入式系统和智能卡系统中很常见
  • 收音机、洗衣机和微波炉这样的设备都已完全被(ROM形式的)软件控制,
    • 软件都采用访问绝对内存地址的寻址方式。
    • 是因为,所有运行的程序都可事先确定
    • 用户不可能在烤面包机上运行他们自己的软件

高端的嵌入式系统(智能手机)有复杂的OS,但简单嵌入式系统不。

  • 某些情况下可用一种简单的OS,
    • 它只是一个被链接到应用程序的库
    • 库为程序提供I/O和其他任务所需的系统调用
  • 操作系统作为库实现
    • 如流行的e-Cos OS

3.2 一种存储器抽象:地址空间

  • 物理地址暴露给进程有严重问题
    • 第一,如果用户程序可寻址内存的每个字节,它们就可很容易地(故意地或偶然地)破坏OS,从而使系统慢慢地停止运行(除非使用
      特殊的硬件进行保护,如IBM360的锁键模式)。
      • 即使只有一个用户进程运行,此问题也存在
    • 第二,想同时运行(如果只有一个CPU就轮流执行)多个程序是很困难的。
      • 个人计算机上,同时打开几个程序很常见,一个当前正在工作,其余的按下鼠标时才被**
  • 在系统中没有对物理内存的抽象的情况下,很难实现上述情景,因此,需其他办法

3.2.1 地址空间的概念

  • 要使多个应用程序同时在内存中且不互相影响,需解决俩问题:
    • 保护和重定位。
  • 来看一个原始的对前者的解决办法,它曾被用在IBM360上:
    • 给内存块标记上一个保护键,并且比较执行进程的键和其访问的每个内存字的保护键。
    • 然而,这种方法本身并没有解决后一个问题,虽然这个问题可以通过在程序被装载时重定位程序来解决,但这是缓慢且复杂的解法。

更好的办法是创造新的存储器抽象:地址空间

  • 进程的概念创造了一类抽象的CPU以运行程序一样,
    • 地址空间为程序创造了一种抽象的内存。
  • 地址空间是一个进程可用于寻址内存的一套地址集合
    • 每个进程都有一个自己的地址空间,
    • 且这个地址空间独立于其他进程的地址空间(除进程要共享它们的地址空间外)

地址空间的概念非常通用,在很多场合中出现。

  • 如电话号码,在很多国家,本地电话号码通常是一个7位的数字。
  • 因此,电话号码的地址空间是从0 000 000到9 999 999,虽然一些号码并没有被使用,比如以000开头的号码。
  • 随着手机、调制解调器和传真机数量的增长,这个空间变得越来越不够用了,从而导致需要使用更多位数的号码。
  • x86的I/O端口的地址空间从0到16383。
  • IPv4的地址是32位的数字,因此它们的地址空间从0到23212^{32}-1(也有一些保留数字)。

地址空间也可是非数字的,以“.com”结尾的网络域名的集合也是地址空间。

  • 这个地址空间是由所有包含2~63个字符并且后面跟着“,com”的字符串组成的,组成这些字符串的字符可以是字母、数字
    和连字符。到现在你应该已经明白地址空间的概念了,它是很简单的。

难的是给每个程序一个独有的地址空间,使程序中的地址28所对应的物理地址与另个程序中的地址28所对应的物理地址不同。

  • 下面我们讨论一个简单的方法,这个方法曾经很常见,
    • 但是在有能力把更复杂(而且更好)的机制运用在现代CPU芯片上之后,
    • 这方法就不再用了

基址寄存器与界限寄存器

  • 这个简单的解决办法是使用动态重定位,简单地把每个进程的地址空间映射到物理内存的不同部分。
  • 从CDC 6600(世界上最早的超级计算机)到 Intel 8088(原始 IBM PC的心脏),所使用的经典办法是给每个CPU配置两个特殊硬件寄存器,
    • 叫基址寄存器和界限寄存器。
  • 当使用基址寄存器和界限寄存器时,程序装载到内存中连续的空闲位置且装载期间无须重定位,如图3-2c所示。
  • 当一个进程运行时,程序的起始物理地址装载到基址寄存器中,程序的长度装载到界限寄存器中。
  • 在图3-2c中,当第一个程序运行时,装载到这些硬件寄存器中的基址和界限值分别是0和16384。
  • 当第二个程序运行时,这些值分别是16384和32768。
  • 如果第三个16KB的程序被直接装载在第二个程序的地址之上并且运行,这时基址寄存器和界限寄存器里的值会是32768和16384

每次一个进程访内存,取指,读或写一个数据字,CPU硬件会在把地址发送到内存总线前,自动把基址值加到进程发出的地址值上。

  • 同时,它检査程序提供的地址是否等于或大于界限寄存器里的值。
  • 如果访问的地址超过了界限,会产生错误并中止访问。
  • 这样,对图3-2c中第二个程序的第一条指令,程序执行
  • JMP 28
  • 指令,但是硬件把这条指令解释成
  • JMP 16412
  • 所以程序如我们所愿地跳转到了CMP指令。
  • 在图3-2c中第二个程序的执行过程中,基址寄存器和界限寄存器的设置如图3-3

3内存管理

用基和界是给毎个进程提供私有地址空间的非常容易的方法,

  • 因为每个内存地址在送到内存前,都自动先加上基址寄存器的内容。
  • 很多实际系统中,对基和界会以一定的方式保护,使得只有OS可修改
  • 在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),该策略甚至能使程序在只有一部分被调入内存的情况下运行。
    下面先讨论交换技术,3.3节我们将考察虚拟内存。

交换系统的操作如图3-4所示。

3内存管理

  • 开始时内存中只有进程A。
  • 之后创建进程B和C或者从磁盘将它们携入内存。
  • 图3-4d显示A被交换到磁盘。
  • 然后D被调入,B被调出,最后A再次被调入。
  • 由于A的位置发生变化,所以在它换入的时侯通过软件或者在程序运行期间(多数是这种情况)通过硬件对其地址进行重定位。
  • 例如,基址寄存器和界限寄存器就适用于这种情况

交换在内存中产生了多个空闲区(hole,也称为空洞),通过把所有的进程尽可能向下移动,有可能将这些小的空闲区合成一大块。

  • 该技术称为内存紧缩( memory compaction)。
  • 通常不进行这个操作,因为它要耗费大量的CPU时间。
  • 例如,一台有16GB内存的计算机可以每8ns复制8个字节,它紧缩全部内存大约要花费16s。

有一个问题值得注意,即当进程被创建或换入时应该为它分配多大的内存。

  • 若进程创建时其大小是固定的并且不再改变,则分配很简单,操作系统准确地按其需要的大小进行分配,不多也
    不少。

但是如果进程的数据段可以增长,例如,很多程序设计语言都允许从堆中动态地分配内存,那么当进程空间试图增长时,就会出现问题。

  • 若进程与一个空闲区相邻,那么可把该空闲区分配给进程供其增大。
  • 另一方面,若进程相邻的是另一个进程,那么要么把需要增长的进程移到内存中一个足够大的区域中去,
    要么把一个或多个进程交换出去,以便生成一个足够大的空闲区。
  • 若一个进程在内存中不能增长,而且磁盘上的交换区也已满了,那么这个进程只有挂起直到一些空间空闲(或者可以结束该进程)

如果大部分进程在运行时都要增长,为了减少因内存区域不够而引起的进程交换和移动所产生的开内存管理销,一种可用的方法是,当换入或移动进程时为它分配一些额外的内存。

  • 然而,当进程被换出到磁盘上时,应该只交换进程实际上使用的内存中的内容,将额外的内存交换出去是一种浪费。
  • 在图3-5a中读者可看到一种已为两个进程分配了增长空间的内存配置。

3内存管理

相关文章: