分配时,是在堆的空闲链表上,查找到第一块空闲的并且足够大的内存,然后对这块内存进行分割,分割一块足够的内存给调用者,剩余部分作为一个新的节点链接到空闲链表里面去;释放时,查找与被释放内存区域连续的节点,如有找到,则合并,如无,则作为一个新的节点放回空闲链表里面去。
多次分配、释放内存操作后,可能会产生大量的外部碎片(下面红色表示已分配的内存块,绿色表示空闲的内存块)。
例如,经过多次分配后,内存块分布如下所示:
|-------------------------------------------------------------------|------|-------------------------------------------------------------------|------|-------------------------------------------------------------------|
然后有部分内存被释放了,内存块如下所示:
|-------------------------------------------------------------------|------|-------------------------------------------------------------------|------|-------------------------------------------------------------------|
此时,用户申请一块如下所示大小的内存:
|------------------------------------------------------------------------------------------------|
虽然空闲的内存总量比要申请的内存的两倍还多,但由于空闲的内存之间存在着碎片,空闲内存不连续,导致内存无法分配。
应用使用虚拟地址,当内存间存在一定数量的外碎片时,整理内存(将已分配内存拷贝到连续的区域),再将虚拟内存重新进行映射(调整内存映射表);该方法需要芯片支持虚拟内存映射,即芯片带有MMU;
分配内存时根据大小确定分配内存的位置,使大小相近的部分集中在一起,避免由于大内存被小内存(碎片)分割,该方式对硬件无要求;
a. 如1.3.2;
b.分配小内存时,要尽可能满足数量上的需求;
c.分配大内存时,要尽可能满足大小上的要求;
需求a要求大小相近的放在一起,需求b和c要求不能用固定位置的方式放,即小内存和大内存是共享一块堆内存空间的。
所以,实现应该是分配内存时,搜索空闲的内存链表,在满足要求的所有节点中,找出这样的节点:
a.如果内存块大小与之相差较小,无需分割,直接分配给用户;
b.如找不到a,那么,需要分割,分割后最好做到分割后,再次申请相近大小的内存时,找到a这样的节点;
c.如找不到b,那么,应分割最小的节点,减少在大内存间产生小碎片的可能性;
a. 注意到实现方式点b,它实际上暗示了该实现方式用的就是二分法;
b. 上面一点结合实现方式点c,提出的需求是,如果二分法分完后,还有大块内存存在,应再产生实现点b,亦即是说,对残余的内存逐步二分,直至产生实现方式b;
a. 初始内存状态如下所示:
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
b.分配内存:
|----------------------|----------------------|-------------------------------------------|------------------------------------------------------------------------------------------------|
c.再分配内存:
|----------------------|-----------|----------|-------------------------------------------|------------------------------------------------------------------------------------------------|
d.再分配内存:
|----------------------|-----------|----------|---------------------|---------------------|------------------------------------------------------------------------------------------------|
e.再分配内存(看到了类聚的效果):
|----------------------|-----------|----------|---------------------|---------------------|------------------------------------------------------------------------------------------------|
f.再分配内存:
|----------------------|-----------|----------|---------------------|---------------------|------------------------------------------------------------------------------------------------|
g.释放两块内存:
|----------------------|-----------|----------|-------------------------------------------|------------------------------------------------------------------------------------------------|
h.申请大内存(可以看到,由于小内存集中在一起,没有分割内存,所以可以利用释放掉的两块内存来拼成大内存使用):
|----------------------|-----------|----------|-------------------------------------------|------------------------------------------------------------------------------------------------|
a.内存池的使用:因为使用的是二分法类聚,应先确定二分法分的类型的个数,假设为BUDDY_LEVEL_COUNT,那么最多分配的内存块个数是2的BUDDY_LEVEL_COUNT - 1次方,假定为BUDDY_PAGE_COUNT;假定最小分配大小为BUDDY_PAGE_SIZE,那么内存池的大小应为BUDDY_PAGE_COUNT X BUDDY_PAGE_SIZE;
b.内存管理数据结构:为了快速找到最小的,满足分配需求的空闲内存块,那么,每种内存类都应有一个空闲内存链表,假设链表节点为Buddy_Memory_Node,那么,存储链表的数组应为Buddy_Memory_Node * arrListFree[BUDDY_LEVEL_COUNT],数组里面存储每个等级的内存链表;Buddy_Memory_Node可以使用空闲内存直接存储;为了快速可合并的节点,节点最好是顺序链接起来;
c.初始化:arrListFree里内存单元最大的类的链表指向内存池,其他的为空;
d操作:如2.1.4所示;