220 likes | 332 Views
数据结构与算法 Data Structure Algorithms 烟台南山学院信息科技学院 数据结构与算法教学组. 内存的初始状态. U 1. U 2. U 3. U 4. 系统运行初期. 系统运行若干时后. U 2. U 4. 动态存储管理概述. 存储原理 计算机内存在刚开始工作时,空闲部分是一个整块的连续区域; 随着用户进入系统,多次申请和释放内存以后,空闲部分不再是连续的了,而成了多个空闲区。常用链表管理,即可利用空间表 动态存储管理 指系统随机地根据用户程序申请空间的大小,进行分配空间和回收不用空间所进行的内存管理。. 0. 0. 0.
E N D
数据结构与算法Data Structure Algorithms 烟台南山学院信息科技学院 数据结构与算法教学组
内存的初始状态 U1 U2 U3 U4 系统运行初期 系统运行若干时后 U2 U4 动态存储管理概述 • 存储原理 • 计算机内存在刚开始工作时,空闲部分是一个整块的连续区域; • 随着用户进入系统,多次申请和释放内存以后,空闲部分不再是连续的了,而成了多个空闲区。常用链表管理,即可利用空间表 • 动态存储管理 • 指系统随机地根据用户程序申请空间的大小,进行分配空间和回收不用空间所进行的内存管理。
... 0 0 0 200 300 150 av tag space size link tag=0:空闲 tag=1:使用 可利用空间表及分配方法 • 可利用空间表 • 将所有内存空闲块用链表连接而成; • 空闲块的大小可以是全相同的,也可以是分成若干固定大小的,还可以是随机大小的。
分配方法 • 首次拟合法 • 分配找到的第一个不小于n的空闲块的一部分。 • 操作方便,查找快捷; • 最佳拟合法 • 分配不小于n且最接近n的空闲块的一部分。 • 尽可能将大的空闲块留给大程序使用; • 最坏拟合法 • 分配不小于n且是最大的空闲块的一部分。 • 尽可能减少分配后无用碎片;
内存的分配与回收 • 分配 • 根据申请内存大小利用相应的分配策略分配给用户所需的空间; • 若分配块大小与申请大小相差不多,则将此块全部分给用户; • 否则,就需将分配块分为两部分,一部分给用户使用,另一部分继续留在可利用空间表中。 • 回收 • 测试回收块前后相邻内存块是否空闲; • 若是则需将回收块与相邻空闲块合并成较大的空闲块,再链入可利用空间表中。
8.3 边界标识法 • 用以进行动态分区分配的一种管理方法 • 可利用空间表的结点结构
边界标识法的数据结构 struct BLK { struct BLK *llink; #define ulink llink //ulink直接利用llink域 int tag; int size; /* 不包括头和脚占用的空间,必须是sizeof(struct BLK)的倍数 */ struct BLK *rlink; }; #define FootLoc(p) \ (struct BLK *)((char *)p + sizeof(struct BLK) + p->size)
分配算法 • 将所有的空闲块链接在一个双重循环链表结构的可利用空间表中; • 分配可按首次拟合法或最佳拟合法进行; • 为避免过多碎片,设一常量EPSILON • m-n EPSILON将大小为m的块全部分出 • > EPSILON分出n大小,剩余留下
分配算法描述 void *mem_alloc(int nbytes) { #define u sizeof(struct BLK); 修正nbytes = (nbytes + u -1) / u * u; /*u为2n有:nbytes = (nbytes+u-1)&~(u-1) */ 从链表中找满足size>=nbytes的块p; if (p==NULL) return NULL; if (p->size – nbytes <= EPSILON) { p脱离空闲链; p->tag = FootLoc(p)->tag = 1; return p+1; } else { p->size -= nbytes + 2 * sizeof(struct BLK); f = FootLoc(p); f->tag = 0; f->uplink = p; p = f + 1; p->tag = 1; p->size = nbytes; f = FootLoc(p); f->tag = 1; f->uplink = p; return p+1; } }
回收算法 • 根据回收缓冲区地址,找到当前内存块的管理信息 p = (struct BLK *)buf – 1; • 与此内存块紧邻的,处于高地址端的内存块的管理信息 h = (struct BLK *)((char *)(p+2) + p->size); • 与此内存块紧邻的,处于低地址端的内存块的管理信息 l = (p-1)->uplink; • 判l->tag和h->tag,知低端/高端内存块是否空闲,决定是否和低端/高端空闲块合并
回收算法描述 void mem_free(void *buf) { p = (struct BLK *)buf – 1; p->tag = FootLoc(p)->tag = 0; h = (struct BLK *)((char *)(p+2) + p->size); if (h->tag == 0) { h脱离空闲块链表; FootLoc(h)->uplink = p; p->size += h->size + 2 * sizeof(struct BLK); } l = (p-1)->uplink; if (l->tag) { 将p加入到空闲块链表; } else { l->size += p->size + 2 * sizeof(struct BLK); FootLoc(p)->uplink = l; } } (上述算法未考虑p为可分配空间第一块和最后一块的特殊情况)
边界表示法的问题 • 查找适合需要的块,需要较多的时间 • 查找适合需要的块的策略(最先/最佳/最坏),每种都有缺陷 • 碎片问题
8.4 伙伴系统 • 伙伴系统的空闲块大小以2k (k=n, n+1,n+2,…,m)标定。按k值不同组织成多个空闲块链表av[]。设n=5,最小分配32字节,用一张位图维护内存中每个32字节块的使用情况(1:已分配,0:空闲) • 系统开始时只有一个空闲块,大小为2m • 分配(访问链表av[k]) • 将满足用户需求的2k大小的空闲块分配给用户 • av[k]链表空,就找2k+1的空闲块,将其分为两半(互称伙伴),一半给用户,另一半加入av[k]链表中 • 回收: • 只有伙伴才合并,并将合并后的新空闲块加入上一级大小的空闲块链表中。
{ Z+2k当 Z mod 2k+1 = 0 Buddy(k,Z)= Z - 2k当 Z mod 2k+1 = 2k 伙伴系统(续) • 首地址为Z,大小为2k的内存块,其伙伴为 例:大小128字节,首地址为0xE580的内存块,其伙伴为0xE500 例:大小128字节,首地址为0xF600的内存块,其伙伴为0xF680
分配和回收算法 • 分配算法 当用户申请大小为n的内存请求,在可利用空间表上寻找结点大小与n相匹配的表 • 表非空,分配表中第一个内存块 • 表空,从更大的非空表中查找,直到找到一个空闲块,切割出所需要大小的块 • 未分配部分,插入到相应的空闲表中 • 回收算法 判断伙伴是否为空闲块(回收算法需了解释放的块大小) • 否,将释放的空闲块插入到相应表中; • 是,找到伙伴,伙伴出队,合并 • 合并后,判断合并后的块的伙伴是否是空闲块,如果是,继续合并成更大的块。依次重复,直到归并后的块的伙伴不空闲。再插入到相应的空闲块表中。
举例:阶段1 分配256,128,64之后,分别是B,C,D
举例:阶段2 申请128(块F),释放128(块C)
举例:阶段3 释放64(块D)
伙伴系统的数据结构 struct BLK { int kval; struct BLK *prev, *next; }; struct BLK *av[M]; // 多个空闲链表 应当使用双向循环链表结构
Buddy算法分析 • 可以立即找到空闲块 • 避免了碎片问题 • 申请内存总是以2n字节满足要求,块内浪费 例如:申请130字节,会分得256字节;申请1514字节,会分得2048字节 • 申请/释放可能会导致连锁切块/合并,影响系统效率 例如:当前只有一块空闲,块大小1M, 申请40字节,会导致12次切块,用完立即归还,导致12次合并。如果程序循环式申请40字节,然后归还内存,会导致系统频繁忙碌。
其它算法 • Lazy-Buddy 解决申请/释放可能会导致连锁切块/合并。该合并时,通过一定“lazy”策略,暂时不合并,在合适的时机合并 • Slab 最早出现在Solaris, 在Linux中采用 避免了碎片问题,并且与内存的分页系统很好配合工作,分配归还的效率很高。 基本思路:每次申请一个内存页面(4096字节)或者多个,切割成所需要的固定大小。不同大小的内存申请,使用不同的空闲队列。
作业 • 边界标识法需要哪些管理信息?给出相应的数据结构定义。在回收用户指定的缓冲区buf时,如何判断紧邻该缓冲区的上端内存和下端内存是否空闲?给出回收算法。 • 使用伙伴算法管理地址从0开始的16M字节内存,内存分配的最小单位为128字节。系统释放内存地址为十六进制BBCC00的1024字节内存: (1) 如何确定这一待释放内存块的伙伴的地址?地址值是多少? (2) 如何判断它的伙伴能否与它合并