计算机内存管理介绍

计算机操做系统内存管理是十分重要的,由于其中涉及到不少设计不少算法。《深刻理解计算机系统》这本书曾提到过,如今操做系统存储的设计就是“带着镣铐跳舞”,形成计算机一种一种容量多,速度快的假象。
程序员

包括如今不少系统好比数据库系统的设计和操做系统作法类似。因此在学习操做系统之余我来介绍并总结一些操做系统的内存管理。面试


首先咱们看一下计算机的存储层次结构算法

按照金字塔结构能够分为四种类型: 寄存器,快速缓存,主存和外存。而数据库

寄存器和L1缓存都在Processor内部。在金字塔中,越往下价格越低速度越慢但容量越大。缓存

还有两种存储空间须要分清:bash

•地址空间:又称逻辑地址空间,源程序通过编译后获得的目标程序,存在于它所限定的地址范围内,这个范围称为地址空间。地址空间是逻辑地址的集合。•内存空间: 又称存储空间或物理地址空间。是指主存中一系列存储信息的物理单元(划重点)的集合,这些单元的编号称为物理地址或绝对地址。服务器

简言之就这两个空间分别是程序员可以观测到的存储空间和真实的物理空间。数据结构

需求与管理的目标

需求:并发

•每一个程序员但愿没有第三方因素干扰程序运行•计算机但愿将有限的资源尽量为多个用户提供服务app

为了知足需求的目标:

•计算机至少同时存在一个用户程序和一个服务器程序(操做系统内核管理)•每一个程序互不干扰,因此其地址空间应该相互独立。•每一个程序使用的空间应该被保护,最怕运行的时候程序中断。就和看电影的时候没法播放同样难受。

程序的内存管理

操做系统在内存中的位置有如下三种可能

只有一个程序的环境下的内存管理

此时整个内存只有两个程序,即用户程序和操做系统。

操做系统所占的空间是固定的,则用户程序空间也是固定的,所以能够将用户程序永远加载到同一个地址,即用户程序永远从同一个地方开始运行。这种状况下,用户程序地址能够在运行以前就能够计算出来。

咱们经过加载器计算程序运行以前的物理地址静态翻译。此时既不须要额外实现地址独立和地址保护。由于用户不须要知道物理内存的相关知识,并且也没有其它用户程序。

多个程序的环境下的内存管理

此时用户的程序空间须要经过分区来分给多个不一样的程序了。每一个应用程序占用一个或几个分区,这种分配支持多个程序并发执行,但难以进行内存分区的共享。

其中分区有两种方法:

一种方法: 固定(静态)式 分区分配, 让程序适应分区

顾名思义就是把内存划分为若干个固定大小的连续分区,这几个分区或者大小相等以适合多个相同程序并发,或者大小不等的分区以适合不一样大小的程序。

这种分配方法优势很明显,在于很是容易实现,开销小。

缺点就是会产生不少内部碎片(也就是未被利用的存储空间),固定的分区总数也限制了并发执行的程序数目。咱们简单介绍下静态分配的几种方法。

•单一队列的分配方式

  • 多队列分配方式


•固定分区管理

先使用表进行大小初始化,固定分区大小

另外一种方法:可变(动态)式 分区分配, 让分区适应程序

此时分区的边界能够移动,但也产生了分区与分区之间狭小的外部碎片。

在可变分区中,知道内存的空闲空间大小就十分重要了。OS经过跟踪内存使用计算出内存有多少空闲。跟踪的方法有两种:

位图表示法:

也就是所谓的bitmap,用每一位来存放某种状态。将内存每个分配单元赋予一个判断的用于判断状态的字位,字位取值位0表示单元闲置;字位为1表示单元被占用

•特色

•空间成本固定:不受内存程序数量影响• 时间成本低:• 操做的时候只须要将状态值改变缺乏容错能力:因为内存单元发生错误的时会将状态值改变,对操做系统来说,这个状态值是由于发生错误发生的改变仍是原来的状态很难判断。

链表表示法

将分配单元按照是否闲置连接,P表明这个空间被占用,H表明这个这是一片闲置空间。为了方便遍历查询,每一个程序空间的结点接着一个空闲空间的结点每一个链表结点还有一个起始地址,与分配单元的大小,用代码表示为

enum Status{P=0,H=1};struct LinkNode{    enum Status status;//P表示程序,H表示空闲    struct LinkNode *begin_address;//起始地址    size_t size;//闲置空间大小    struct LinkNode *next;};复制代码

•特色

•空间成本:取决于程序数量• 时间成本:链表不停的遍历速度很慢,同时还要进行链表的插入和删除修改。• 有必定的容错能力,能够经过程序空间结点和空闲空间结点相互验证。

可变分区的内存分配

OS经过上面两种跟踪方法知道内存空闲容量,而如今操做系统通常都以链表的形式进行内存空闲容量跟踪。若是有新的程序须要读入内存,可变分区就要对空闲的分区进行内存分配。

内存分配使用两张表:已分配分区表和未分配分区表。用C++描述以下:

//未分配分区表struct FreeBlock {    int id;         // 内存分区号    int address;    // 该分区的首地址    unsigned length;     // 分区长度};​//已分配分区表struct AllocatedBlock {    int id;         // 内存分区号    int address;    // 该分区的首地址    int pid;        // 进程 ID    unsigned length;     // 分区长度};复制代码

而后OS用双向链表将全部未分配分区表进行串联

struct{    FreeBlock data;    Node* prior;    Node* next;}Node;复制代码

未分配分区表在整个系统空间上的结构以下:

基于 顺序搜索 的分配算法:

这里咱们介绍四种基于顺序搜索的寻找空闲存储空间的算法:

•首次适应算法( First Fit ) :每一个空白区按其地址顺序连在一块儿,从这个空白区域链的始端开始查找,选择第一个足以知足请求的空白块。• 下次适应算法( Next Fit ) :将存储空间中空白区构成一个循环链,每次为存储请求查找合适的分区时,老是从上次查找结束的下一个空闲块开始,只要找到一个足够大的空白区,就将它划分后分配出去。• 最佳适应算法( Best Fit ) : 为一个做业选择分区时,老是寻找其大小最接近(小于等于)于做业所要求的存储区域。• 最坏适应算法( Worst Fit ) :为做业选择存储区域时,老是寻找最大的空白区。


算法举例!!

系统中空闲分区表以下按照地址递增次序排列,现有三个做业分配申请内存空间100K,30K,7K

区号 大小 地址 状态
1 32K 20K 未分配
2 8K 52K 未分配
3 120K 60K 未分配
4 331K 180K 未分配

•首次适应:

从上到下寻找合适的大小

•申请做业100K,从低地址到高地址找到3号分区,分配完后3号分区起始地址变为100K+60K=160K,剩余空间为120K-100K=20K•申请做业30K,从低地址到高地址找到1号分区,分配完后1号分区起始地址变为20K+30K=50K,剩余空间为32K-30K=2K•申请做业7K,从低地址到高地址找到2号分区,分配完后2号分区起始地址变为52K+7K=59K,剩余空间为8K-7K=1K结论:优先利用内存低地址部分的空闲分区。但因为低地址部分不断被划分,留下许多难以利用的很小的空闲分区(碎片或零头) ,而每次查找又都是从低地址部分开始,增长了查找可用空闲分区的开销。

下次适应

•申请做业100K,找到3号分区,分配完后3号分区起始地址变为100K+60K=160K,剩余空间为120K-100K=20K•申请做业30K,从3号分区后继续出发,找到4号分区,分配完后4号分区起始地址变为180K+30K=210K,剩余空间为331K-30K=301K•申请做业7K,从4号分区后继续出发,找到1号分区,分配完后1号分区起始地址变为20K+7K=27K,剩余空间为32K-7K=25K结论:使存储空间的利用更加均衡,不导致小的空闲区集中在存储区的一端,但这会致使缺少大的空闲分区。

最佳适应算法

•申请做业100K,找到最适合的3号分区,分配完后3号分区起始地址变为100K+60K=160K,剩余空间为120K-100K=20K•申请做业30K,找到最适合的1号分区,分配完后1号分区起始地址变为20K+30K=50K,剩余空间为32K-30K=2K•申请做业7K,找到最适合的2号分区,分配完后1号分区起始地址变为52K+7K=59K,剩余空间为8K-7K=1K
结论:若存在与做业大小一致的空闲分区,则它必然被选中,若不存在与做业大小一致的空闲分区,则只划分比做业稍大的空闲分区,从而保留了大的空闲分区。最佳适应算法每每使剩下的空闲区很是小,从而在存储器中留下许多难以利用的小空闲区(碎片) 。

最坏适应算法

•申请做业100K,找到4号分区,分配完后3号分区起始地址变为180K+60K=240K,剩余空间为331K-100K=231K•申请做业30K,此时被分配过的4号分区依然容量最大,因而仍是找到4号分区,分配完后4号分区起始地址变为240+30K=250K,剩余空间为231K-30K=201K•申请做业7K,此时被分配过的4号分区依然容量最大,找到4号分区,分配完后4号分区起始地址变为250+7K=257K,剩余空间为201K-7K=194K
结论:老是挑选知足做业要求的最大的分区分配给做业。这样使分给做业后剩下的空闲分区也较 大,可装下其它做业。因为最大的空闲分区老是因首先分配而划分,当有大做业到来时,其存储空间的申请每每会得不到知足。


基于顺序搜索的分配算法实际上只适合小型的操做系统,大中型系统使用了是比较复杂的索引搜索的动态分配算法。

如何回收内存

•回收分区上邻接一个空闲分区,合并后首地址为空闲分区的首地址,大小为两者之和。•回收分区下邻接一个空闲分区,合并后首地址为回收分区的首地址,大小为两者之和。•回收分区上下邻接空闲分区,合并后首地址为上空闲分区的首地址,大小为三者之和。•回收分区不邻接空闲分区,这时在空闲分区表中新建一表项,并填写分区大小等信息。

iPad画了一个简单的示意图以下:

最后

内存分配其实是操做系统很是重要的一环,若是仅限于理论而不写代码实践则容易迷惘,不少具体的实现与都比较困难。如上面的基于顺序搜索的最佳适应算法,好比几个分区的表示方法,都用到了数据结构和算法的知识。若是能用C或者C++完成上述几个算法和操做的具体实现,相信必定会大有脾益的。


Java 极客技术公众号,是由一群热爱 Java 开发的技术人组建成立,专一分享原创、高质量的 Java 文章。若是您以为咱们的文章还不错,请帮忙赞扬、在看、转发支持,鼓励咱们分享出更好的文章。

关注公众号,你们能够在公众号后台回复“掘金”,得到做者 Java 知识体系/面试必看资料。


相关文章
相关标签/搜索