初识内存管理

Posted by persuez on January 3, 2019

每个程序员都梦想拥有这样的存储器:它是私有的、容量无限大的、速度无限快的,并且是永久性的(即断电时不会丢失数据。–《现代操作系统》

但是梦想是美好的,现实就是挺现实的。除此之外,经过多年的探索,人们提出了分层存储器体系的概念,在这个体系中,计算机有若干兆(MB)快速、昂贵且易失性的高速缓存,数千兆(GB)速度与价格适中且同样易失性的内存,以及几兆兆(TB)低速、廉价、非易失性的磁盘存储,另外有诸如 DVD 和 USB 等可移动存储装置。操作系统的一个工作就是将这个存储体系抽象为一个有用的模型并管理这个抽象模型。

操作系统中管理分层存储体系的部分称为存储管理器。它的任务是有效地管理内存,即记录哪些内存是正在使用,哪些内存是空闲的;在进程需要时为其分配内存,在进程使用完成后释放内存。

由于最底层的高速缓存的管理由硬件来完成(计算机体系结构中有学),这里先集中学习针对编程人员的内存模型,以及怎样优化管理内存。至于硬盘的抽象与管理,以后再学。

无存储器抽象

最简单的存储器抽象是没有抽象。早期的计算机都没有存储器抽象。也就是说,每一个程序都直接访问物理内存。当一个程序执行指令: MOV REGISTER1, 1000时,计算机会将位置为 1000 的物理地址的内容移到 REGISTER1 中。因此,那时呈现给程序员的存储器模型就是简单的物理内存:从 0 到某个上限的地址集合,每一个地址对应一个可容纳一定数目二进制位的存储单元,通常是 8 个。

在这种直接面向物理内存的情况下,要想在内存中同时运行两个程序几乎是不可能的。如果一个程序在某个地址写入内容,另一个程序也在相同的地址写入一些内容,那将会覆盖掉第一个程序所写入的内容。

不过还是有一些可行选项的:

  • 通过多线程编程实现并发。因为在引入线程时就假设一个进程中的所有线程对同一内存映像是可见的,那么实现并发也就不成问题了。但是这个想法并没有被广泛使用,因为人们通常希望能够在同一时间运行没有关联的程序,而这正是线程抽象不具备的能力。更进一步地,一个没有存储器抽象的系统也不大可能具有线程抽象的功能。
  • 通过交换并发运行多个程序。操作系统只要把当前内存中的内容保存到磁盘文件中(可能还有寄存器内容),然后再把下一个程序读入到内存中运行即可。只要在某一个时间内存中只有一个程序,那么就不会发生冲突了。
  • 在特殊的硬件帮助下并发运行多个程序。IBM 360 早期模型是这样解决的:将内存划分为 2KB 的块,每个块被分配一个 4 位的保护键,保护键存储在 CPU 的特殊寄存器中。一个内存 1MB 的机器只需要 512 个 4 位寄存器,容量总共为 256 字节。 PSW(Program Status Word,程序状态字)中存有一个 4 位码。一个运行中的进程如果访问保护键和其 PSW 不同的内存,360 的硬件会捕获到这一事件。因为只有操作系统可以修改保护键,这样就可以防止用户进程之间、用户进程和操作系统之间(操作系统有可能位于 RAM 中)的互相干扰。但是,这样做有一个重要的缺陷。如下图所示,假设有两个程序,每个大小各位 16 KB,如图 a 和图 b 所示。前者的阴影表示它和后者使用不同的保护键。 重定位问题的说明 第一个程序一开始就跳转到地址 24,那里是一条 MOV 指令。第二个程序一开始就跳转到地址 28,那里是一条 CMP 指令。当两个程序被连续加载到内存中从 0 开始的地址时,内存中的状态就如图 c 所示。在这个例子中,我们假设操作系统运行在高地址处,图中没有画出。程序装载完就可以运行了,由于它们的内存键不同,它们不会破坏对方的内存。但在另一方面会出现问题。当第一个程序开始运行时,它执行了JMP 24指令,然后正确地执行了该指令。当该程序运行了一段时间后操作系统决定运行第二个程序,即装载在第一个程序之上的地址 16384 处的程序。问题来了,这个程序的第一条指令是JMP 28,这条指令会使程序条状到第一个程序的ADD指令处,而不是事先设定的跳转到CMP指令。由于对内存地址的不正确访问,这个程序很可能在 1 秒内就崩溃了。这里很关键的问题是两个程序都引用了绝对物理地址,而这正是最需要避免的,我们希望每个进程都使用一套私有的本地地址来进行内存寻址。对于这个问题 IBM 360 的补救方案是在第二个程序装载到内存时,使用静态重定位的技术修改它。它的工作如下:当一个程序装载到地址 16384 时,常数 16384 被加到每一个程序地址上。虽然这个机制是可行的,但并不是一个通用的解决方法,同时会减慢程序的装载速度。而且,它需要给所有的可执行程序提供额外的信息来区分哪些内存字中存有(可重定位的)地址,哪些没有。毕竟,图 b 中的“28”需要被重定位,但是像MOV REGISTER1 28这样把数 28 送到 REGISTER1 的指令不可以被重定位。装载器需要一定的方法来辨别常数和地址。

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