操作系统核心原理-5.内存管理(上):基本内存管理
操作系统的两个角色分别是魔术师和管理者,在管理者这个角色中,除了CPU之外,内存是操作系统要管理的另外一个重要资源。内存管理需要达到两个目标:一是地址保护,即一个程序不能访问另一个程序的地址空间。二是地址独立,即程序发出的地址应该与物理主存地址无关。这两个目标就是衡量一个内存管理系统是否完善的标准,它是所有内存管理系统必须提供的基本抽象。
操作系统的两个角色分别是魔术师和管理者,在管理者这个角色中,除了CPU之外,内存是操作系统要管理的另外一个重要资源。内存管理需要达到两个目标:一是地址保护,即一个程序不能访问另一个程序的地址空间。二是地址独立,即程序发出的地址应该与物理主存地址无关。这两个目标就是衡量一个内存管理系统是否完善的标准,它是所有内存管理系统必须提供的基本抽象。
一、内存管理二三事
1.1 内存管理的目标
(1)地址保护:一个程序不能访问另一个程序地址空间。
(2)地址独立:程序发出的地址应该与物理主存地址无关。
这两个目标是衡量一个内存管理系统是否完善的标准,它是所有内存管理系统必须提供的基本抽象。
1.2 虚拟内存的概念
虚拟内存的中心思想是将物理主存扩大到便宜、大容量的磁盘上,即将磁盘空间看做主存空间的一部分。可以理解为是将书桌上的比较老的文件先暂时收到抽屉里,用空出来的地方来摊开新的文件。在计算机中,体现在在内存容量不足时将不经常访问的内存空间中的数据写入硬盘,以增加“账面上”可用内存容量的手段(想想我们的内存和硬盘容量对比就知道了)。
但是,如果在书桌和抽屉之间频繁进行文件的交换,工作效率肯定会下降。如果每次要看一份文件都要先收拾书桌再到抽屉里面拿的话,那工作根本就无法进行了。
虚拟内存的优点在于除了让程序员感觉到内存容量大大增加之外,还让程序员感觉到内存速度也增快了。
虚拟内存也有同样的缺点:硬盘的容量比内存大,但也只是相对的,速度却非常缓慢,如果和硬盘之间的数据交换过于频繁,处理速度就会下降,表面上看起来就像卡住了一样,这种现象称为抖动(Thrushing)。相信很多人都有过计算机停止响应的经历,而造成死机的主要原因之一就是抖动。
二、基本内存管理
2.1 单道编程的内存管理
在单道编程环境下,整个内存里面只有两个程序:一个是用户程序,另一个是操作系统。
由于只有一个用户程序,而操作系统所占用的内存空间是恒定的,所以我们可以将用户程序总是加载到同一个内存地址上,即用户程序永远从同一个地方开始执行。
这样,用户程序里面的地址都可以事先计算出来,即在程序运行之前就计算出所有的物理地址。这种在运行前即将物理地址计算好的方式叫做静态地址翻译。下面看看此方式如何达到两个目标。
(1)地址独立:用户在编写程序时无需考虑具体的物理内存,用户程序始终都被加载到同一个物理地址上。
(2)地址保护:整个系统里面只有一个用户程序,因此,固定地址的内存管理因为只运行一个用户程序而达到地址保护。
2.2 多道编程的内存管理
在多道编程环境下,无法将程序总是加到固定的内存地址上,也就是无法使用静态地址翻译。因此,必须在程序加载完毕之后才能计算物理地址,也就是在程序运行时进行地址翻译,这种翻译称为动态地址翻译。
多道编程环境下的内管管理策略有两种:
(1)固定分区
顾名思义,固定分区管理就是讲内存分为固定的几个区域,每个区域大小固定。最下面的分区为OS占用,其他分区由用户程序使用。分区大小通常各不相同,当需要加载程序时,选择一个当前闲置且容量足够大的分区进行加载,如下图所示,这是一种共享队列的固定分区(多个用户程序排在一个共同的队列里面等待分区):
由于程序大小和分区大小不一定匹配,有可能形成一个小程序占用一个大分区的情况,从而造成内存里虽然有小分区闲置但无法加载大程序的情况。这时,我们就想到也许可以采用多个队列,给每个分区一个队列,程序按照大小排在相应的队列里,如下图所示,这时一种分开队列的固定分区:
上图这种方式也有缺点:如果还有空闲分区,但等待的程序不在该分区的等待队列上,就将造成有空间而不能运行程序的尴尬。
(2)非固定分区
非固定分区的思想在于除了划分给OS的空间之外,其余的内存空间是作为一个整体存在的。当一个程序需要占用内存空间时,就在该片空间里面分出一个大小刚刚满足程序所需的空间。再来一个程序时,则在剩下的空间里再这样分出一块来。在这种模式下,一个程序可以加载到任何地方,也可以和物理内存一样大。
例如,一开始内存中只有OS,这时候进程A来了,于是分出一片与进程A大小一样的内存空间;随后,进程B来了,于是在进程A之上分出一片给进程B;然后进程C来了,就在进程B上面再分出一片给C。如此,进程A、B和C的起始地址都不是固定的,如下图所示:
仔细一看,这种方式存在一个重大问题:每个程序像叠罗汉一样累计,如果程序B在成型过程中需要更多空间怎么办?(例如在实际程序中,很多递归嵌套函数调用的时候回造成栈空间的增长)因此,我们可以想到可以再一开始的时候给程序分配空间时就分配足够大的空间,留有一片闲置空间供程序增长使用,如下图所示:
不过,OS怎么知道应该分配多少空间给一个程序呢?分配多了,就是浪费;而分配少了,则可能造成程序无法继续执行。
因此,可以在空间不够时,给程序换一个空间,这种方式将程序倒到磁盘上,再加载到内存中,被称为交换(swap)。但是,如果在交换模式下程序的增长超过了物理内存,就不能再交换了。此时,可以将程序按照功能分成一段一段功能相对完整的单元,一个单元执行完成后再执行下一个单元,这就是重叠(overlay)。
但是,交换内存管理这种方式存在两个重要问题:
(1)空间浪费:随着程序在内存与磁盘间的交换,内存将变得越来越碎片化,即内存将被不同程序分割成尺寸大小无法使用的小片空间。
(2)程序大小受限:这有两层意思,一是指空间增长效率低下(由于磁盘操作耗时,交换出去再找一片更大的空间来增长程序空间的做法效率非常低),二是空间增长存在天花板限制(单一程序不能超过物理内存空间)。
2.3 闲置空间管理
在管理内存的时候,OS需要知道内存空间有多少空闲?这就必须跟踪内存的使用,跟踪的办法有两种:
(1)给每个分配单元赋予一个字位,用来记录该分配单元是否闲置。例如,字位取值为0表示单元闲置,取值为1则表示已被占用,这种表示方法就是位图表示法,如下图所示:
(2)将分配单元按照是否闲置链接起来,这种方法称为链表表示法。如上图所示的的位图所表示的内存分配状态,使用链表来表示的话则会如下图所示:
在链表表示下,寻找一个给定大小的闲置空间意味着找到一个类型为H的链表项,其大小大于或等于给定的目标值。不过,扫描链表速度通常较慢。
参考资料
邹恒明,《操作系统之哲学原理》,机械工业出版社