帕金森定律:不管存储器有多大,程序都可以把它填满。
分层存储体系(memory hierarchy):KB 级寄存器,MB 级快速、昂贵且易失的高速缓存(cache),GB 级速度与价格适中但同样易失的内存,以及 TB 级低速、廉价、非易失的磁盘存储,还有 DVD 和 USB 等可移动存储装置。
操作系统的工作是将这个存储体系抽象为一个有用的模型并管理这个抽象模型。
操作系统中管理分层存储体系的部分称为存储管理器(memory manager)。它的任务是有效地管理内存,即记录哪些内存是正在使用的,哪些内存是空闲的;在进程需要时为其分配内存,在进程使用完后释放内存。
无存储器抽象
在没有存储器抽象的系统中实现并行的一种方法是使用多线程来编程。由于在引入线程时就假设一个进程中的所有线程对同一内存映像都可见,那么实现并行也就不是问题,
在不使用存储器抽象的情况下运行多个程序
操作系统只需要把当前内存中所有内容保存到磁盘文件中,然后把下一个程序读入到内存中再运行即可。只要在某一个时间内存中只有一个程序,那么就不会发生冲突。
在特殊硬件的帮助下(防止用户进程之间相互干扰),即使没有交换功能,并发地运行多个程序也是可能的。但这样会有重定位问题,
- 保护:(IBM 360)给内存块标记上一个保护键,并且比较执行进程的键和其访问的每个内存字的保护键。
- 静态重定位:当一个程序被装载到地址16384时,常数16384被加到每一个程序地址上。装载器还需要一定的方法来辨别地址和常数。
基础内存管理:连续分配
一种存储器抽象:地址空间
把物理地址暴露给进程会带来两个严重问题:
- 如果用户程序可以寻址内存的每个字节,那么它们就可以很容易地破坏操作系统。即使在只有一个用户进程运行的情况下,这个问题也是存在的。
- 在系统中没有对物理内存的抽象的情况下,很难实现同时运行多个程序。
地址空间的概念
要使多个应用程序同时处于内存中并且不相互影响,需要解决两个问题:保护的和重定位。
比无存储器抽象时的保护和静态重定位更好的办法是创造一个新的存储器抽象:地址空间。
就像进程的概念创造了一类抽象的 CPU 以运行程序一样,地址空间为程序创造了一种抽象的内存,是一个进程可用于寻址内存的一套地址集合。每个进程都有一个自己的地址空间,并且这个地址空间独立于其他进程的地址空间。
- 动态重定位
- 基址寄存器
- 界限寄存器
- 当使用基址寄存器和界限寄存器时,程序装载到内存中连续的空闲位置且装载期间无须重定位,当一个程序运行时,程序的起始物理地址装载到基址寄存器中,程序的长度装载到界限寄存器中。
- 使用基址寄存器和界限寄存器重定位的缺点是:每次访问内存都需要进行加法和比较运算。
交换技术
有两种处理内存超载的通用方法。
- 交换技术:即把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘。
- 虚拟内存:能使程序在只有一部分被调入内存的情况下运行。
交换在内存中产生了多个空闲区(hole,也称为空洞),通过把所有的进程尽可能向下移动,有可能将这些小的空闲区合成一大块。该技术称为内存紧缩(memory compaction)。
如果进程在运行时内存需要增长,为了减少因内存不够而引起的进程交换和移动所产生的开销,当换入或移动进程时可以为它分配一些额外的内存。
- 为可能增长的数据段预留空间;
- 为可能增长的数据段和堆栈段预留空间。
空闲内存管理
- 使用位图的存储管理
- 分配单元的大小是一个重要的设计因素。内存的大小和分配单元的大小决定了位图的大小。
- 主要问题:查找位图中指定长度的连续0串是耗时的操作。(为了把一个占 k 个分配单元的进程调入内存)
- 使用链表的存储管理
- 维护一个记录已分配内存段和空闲内存段的链表。其中链表的一个结点或者包含一个进程,或者是两个进程间的一块空闲区。
- 链表中的每一个结点都包含以下域:空闲区(H)或进程(P)的指示标识、起始地址、长度和指向下一结点的指针。
- 当按照地址顺序在链表中存放进程和空闲区时,有以下几种算法可以用来为创建的进程,或从磁盘换入的已存在的进程分配内存(假设存储管理器知道要为进程分配多少内存):
- 首次适配
- 下次适配:每次从上次结束的地方开始搜索。
- 最佳适配:比首次适配算法慢,而且比首次适配和下次适配算法浪费更多的内存,因为它会产生大量无用的小空闲区。
- 最差适配
- 快速适配:为那些常用大小的空闲区维护单独的链表。
- 优点:寻找一个指定大小的空闲区是十分快速的。
- 缺点:在一个进程终止或被换出时,寻找它的相邻块并查看是否可以合并的过程非常费时。
- 伙伴式的内存管理
- 拆分和合并涉及到较多的链表和位图操作。
- Buddy算法的分配原理:
- 假如系统需要4(2*2)个页面大小的内存块,该算法就到free_area[2]中查找,如果链表中有空闲块,就直接从中摘下并分配出去。如果没有,算法将顺着数组向上查找free_area[3],如果free_area[3]中有空闲块,则将其从链表中摘下,分成等大小的两部分,前四个页面作为一个块插入free_area[2],后4个页面分配出去,free_area[3]中也没有,就再向上查找,如果free_area[4]中有,就将这16(2*2*2*2)个页面等分成两份,前一半挂如free_area[3]的链表头部,后一半的8个页等分成两等分,前一半挂free_area[2]的链表中,后一半分配出去。假如free_area[4]也没有,则重复上面的过程,直到到达free_area数组的最后,如果还没有则放弃分配。
- Buddy算法的释放原理:
- 内存的释放是分配的逆过程,也可以看作是伙伴的合并过程。当释放一个块时,先在其对应的链表中考查是否有伙伴存在,如果没有伙伴块,就直接把要释放的块挂入链表头;如果有,则从链表中摘下伙伴,合并成一个大块,然后继续考察合并后的块在更大一级链表中是否有伙伴存在,直到不能合并或者已经合并到了最大的块。
基础内存管理:离散分配
- 分段
- 分页
- 多级页表