可变内存管理,随着内存的不断分配和回收,即使系统中有1MB的内存,也可能因没法分配大小为100KB的连续内存块而造成分配失败。
伙伴系统,可以大大改善这一情况。
伙伴系统的缺点:
问题二的产生主要是因为内存控制块本身也属于内存块的一部分,把控制块单独抽出来,而真正可用的内存块留给用户就可以解决问题。
首先真正的物理内存块被分成了两部分,一部分为内存控制结构所使用,内存初始化函数buddy_init()将逐个初始化这些结构。
剩下的内存是用户可用内存。
这些内存被划分为总多基本块,每个基本块的大小可以通过常量BLOCK_SIZE配置。(默认1<<7字节)
这样内存的分配和回收都是基于序列的了,换句话说,这些基本内存块是从0到n逐个标记的。
在逻辑上这些内存块被组织成了m层,最大层数m可以通过常量LEVEL(14)进行配置。
第0层每个内存块的大小为BLOCK_SIZE,第1层每个内存块的大小为2*BLOCK_SIZE,以此类推,第n层内存块的大小为BLOCKSIZE< 当需要分配2i字节大小的内存块时,用公式log (2i/BLOCK_SIZE),可求出从哪一个逻辑层分配内存。 为了最终对应到物理块,这些逻辑的内存块始终是有序号标记的。 以8个基本内存块大小的内存举例。 为了标记每个逻辑内存块的空闲状态和快速找到一个空闲块,每层需要一个空闲状态位图块、空闲内存块链表数组、空闲内存块链表头三个结构。同时为了回收的效率,还需要为每个逻辑内存块存储逻辑层信息,即原来从哪一个逻辑层分配。 内存控制块acoral_block_ctrl_t是aCoral进行内存分配和回收的关键数据结构,其中的一个重要数据结构unsigned int*bitmap[LEVEL]是描述内存块状态的状态位图块数组,每一层均有一个空闲状态位图块数组bitmap,bitmap实际上是一个二维数组bitmap[m][n],第一个下标代表位图块所在的层数m,第二个下标代表该层的第n个位图块。 状态位图块数组bitmap[m][n]的每个值都是int类型,32位的,每一位要么为1,要么为0. 0~(m-1)层中,相邻的两块内存块由空闲位图块中的一位来标识是否空闲,对于第i层,每个内存块由空闲位图块中的一位来标识是否空闲。 这得从伙伴系统思想说起了,当伙伴系统回收时,如果导致某一层相邻两个内存块都空闲时,就会向上一层回收,将两个伙伴合并成一个更大的内存块。因此,正常情况下不存在两块都空闲的情况,0~(m-1)层相邻的两块内存块只有两种状态:没有空闲块、有一块是空闲的。 虽然通过状态位图块数组解决了回收时复杂度O(n)的问题,但没有解决空闲内存块分配问题,即分配内存时如何查找某一层空闲的内存块。 所以,aCoral增加了空闲块链表数组int *free_list[LEVEL],实现了分配时O(1)的复杂度,同时还可以解决内存控制块(链表的实体)占用部分内存块导致的问题。 首先,定义了空闲位图块链表头数组int free_cur[LEVEL];该数组元素指向第一个空闲位图块的标号。然后free_list[LEVEL]的值指出了下一个空闲位图块。 例如,对于第0层,free_cur[0]=2,那么读取free_list[0][2],得到下一个空闲块,假设其值为4,则读取free_list[0][4],再得到下一个空闲位图块,依次往后,形成一个空闲位图块的表链。 注意,这里的2和4表示的是第0层内存状态位图块的标号,这样2表示此时标号为2的内存状态位图块中的32位中有非0位,即这个非零位所对应的相邻内存块由空闲。 根据前面的描述,只要根据第m层的free_cur[m]找出空闲位图块对应的标号i,然后读取bitmap[m][i]的值,再判断bitmap[m][i]首先出现“1”的那一位,并找到该位对应的内存块序号,便可确定该内存块对应的基本内存块标号,最后得到相应物理地址返回给用户,并将刚才的“1”置为0,如果此时空闲状态位图块变为了0(32位每一位都为0),更改free_cur[m]=free_list[m][i],由此可见,根据空闲内存块链表数组就能快速找到空闲内存块,而对链表的维护的复杂度也是O(1)。 还有一个问题就是,系统回收内存块的时候,传送的是地址,根据地址可以知道这个内存块开始地址对应的基本内存块的标号,但是如何知道这块内存块的大小呢?即从第几层分配呢?我们知道不同层分配的内存块包含的基本内存块是不一样的,0层包含1个基本内存块,1层包含2个基本内存块。 由于标号为奇数的基本内存块肯定是从第0层分配出去(因为偶数的基本内存块由非0层分配出去),因此不用保存其是从第几层分配出去的。 根据前面的描述,在1~m-1层,状态位图块bitmap[m][n]的某一位为1时说明该位图管理的奇数块或偶数基本内存块在使用,但是如果偶数块使用了的话,其对应的acoral_block_t的level值大于0,否则为-1,因此可区分这两种状态。 aCoral伙伴算法能正常工作以前,需要通过buddy_init()进行初始化。 起始地址要增加它的值然后对齐,结束地址要减少它的值然后对齐。 递归把剩下的内存块分配给其它逻辑层。例如最高层(N)的一个内存块大小为16个基本内存块,而给顶层分配完了后,可能只剩下15个基本内存块(不足以形成一个最高层的内存块),则给N-1层分配8个基本内存块大小的内存,N-2层分配4个基本内存块大小,N-3层分配2个,0层分配1个基本内存块大小的内存。 经过buddy_init()对伙伴系统进行初始化后,内存都尽量分配给了高层 ,低层的free_list的值都被置为-1了,这意味着,底层的内存块不能之间被使用,直到剩下的内存不够某层的一个内存块大小,再依次分配给低层。
如图所示,这里的内存共包括8个基本内存块,在逻辑上被组织成了3层。
如果该层有空闲内存块,即可分配。
当这一层没有空闲的内存块时,就向上层申请,最终会得到两个空闲的2i字节大小的内存块。
typedef struct{
int *free_list[LEVEL]; //各层空闲内存块链表
unsigned int *bitmap[LEVEL];//各层空闲位图
int free_cur[LEVEL];//各层空闲内存块链表头
unsigned int num[LEVEL]; //各层内存块个数
char level;//伙伴系统的层数
unsigned int start_add; //伙伴系统管理的内存起始地址
unsigned int end_addr; //末尾地址
unsigned int block_num; //基本内存块的数量,等于num[0]
unsigned int free_num; //剩余基本内存块数量
unsigned int block_size; //基本内存块大小
}
为0表示相邻两块内存块没有空闲的,为1表示相邻两块内存块至少有一块是空闲的。
由于bitmap[m][n]的每个值是32位,而每一位代表相邻两块内存块,所以biitmap[m][n]的每个值可以表示64个内存块的分配情况。
1位只有0和1两种状态,则两块内存块有4种状态:两块都空闲、没有空闲块、只有奇数块空闲、只有偶数块空闲。两个状态如何表示4种状态?
大家可能说,直接查看该层的内存状态位图数组中哪一位为1就可以了,但是如果是这样的话,和遍历链表没有本质区别,复杂度也是O(n)。
因此,不同层分配出去的内存块的起始地址可能相同(对应的基本内存块编号相同),这就需要一个数据结构来保存基本内存块i(i=1,2,3)的起始地址所对应的逻辑内存块大小。typedef struct{
char level;
}acoral_block_t;
如果某个基本内存块尚未分配出去,则level的值为-1。
level的值同时也可用来区分内存块位图管理时的两块兄弟内存块的状态。伙伴算法的初始化
buddy_init()传入参数是start_addr和end_addr,分别是系统可用物理内存的起始和终止地址。#defiine acoral_mem_init(start,end) buddy_init(start,end)
acoral_mem_init((unsigned int)&head_start, (unsigned int)*heap_end);
acoral_block_ctrl_t *acoral_mem_ctrl;
acoral_block_t *acoral_mem_blocks;
unsigned int buddy_init(unsigned int start_adr, unsigned int end_adr)
{
int i, k;
unsigned int resize_size;
unsigned int save_adr;
unsigned int index;
unsigned int num = 1;
unsigned int adjust_level = 1;
int level = 0;
unsigned int max_num, o_num;
unsigned int cur;
start_adr += 3;
start_adr &= ~(4-1);
end_adr &= ~(4-1);
resize_size = BASIC_BLOCK_SIZE;
end_adr = end_adr - sizeof(acoral_block_ctrl_t); //减去内存控制块的大小,剩下的才是可分配内存
end_adr &= ~(4-1);
acoral_mem_ctrl = (acoral_block_ctr_t *)end_adr; //内存控制块的地址
//内存少,不分配
if(start_adr > end_adr || end_adr - start_adr < BASIC_BLOCK_SZE)
{
acoral_mem_ctrl->state = MEM_NO_ALLOC;
return -1;
}
acoral_mem_ctrl->state = MEM_OK;
/*根据基本内存块的值和堆的大小获得最大层数,如基本内存块BLOCK_SIZE=4B,而内存的大小为18B,则最大层数为3,第0层是4B,第1层是8B,第二层16B,num记录系统总内存可以分成多少个BLOCK_SIZE大小的基本内存。*/
while(1)
{
if(end_adr <= start_adr + resize_size)
break;
resize_size = resize_size << 1;
num = num << 1;
adjust_level++;
}
//根据num为最小内存控制块(第0层)acoral_block_t分配空间(每一个最小内存控制块均有一个acoral_block_t),因为一个acoral_block_t的大小就是1B,结束地址减去num正好是最小内存控制块数组的开始地址
acoral_mem_blocks = (acoral_blck_t *)end_adr - num;
save_adr = (unsigned int)acoral_mem_blocks;
//如果层数较小,则最大层用一块构成,如果层数较多,限制层数范围,最大层由多块构成
level = adjust_delevl;
if(adjust_level > LEVEL)
level = LEVEL;
//用刚刚计算的基本内存块num除以32,就变成了所需内存块位图数组bitmap的维数,即需要多个32位的内存位图块。
num = num/32;
//for循环是用来分配0~m-1层内存控制块的空间。由于内存块位图数组bitmap的某一位代表了两块内存块,因此,对于第0层需要num/2个32位的内存位图块,所以num/2,level变量用来记录以该基本内存块为起始地址的内存块分配时所在的层
for(i=0; i<level-1; i++)
{
num = num >> 1; //除去最大层,其它每层的32位图都是64个块构成,所以要除以2
if(num == 0)
{
num = 1; //不足一个位图的,用一个位图表示
}
save_adr -= num*4; //每一个32位位图4个字节
save_adr &= ~(4-1); //四字节对齐
acoral_mem_ctrl->bitmap[i] = (unsigned int *)save_adr;
acoral_mem_ctrl->num[i] = num;
save_adr -= num*4;
save_adr &= ~(4-1); //四字节对齐
acoral_mem_ctrl->free_list[i] = (int *)save_adr;
for(k=0; k<num; k++)
{
acoral_mem_ctrl->bitmap[i][k] = 0;
acoral_mem_ctrl->free_list[i][k] = -1;
}
acoral_mem_ctrl->free_cur[i] = -1;
}
//最大内存块层不足一个位图的,用一个位图表示,这里num没有除以二,因为最高内存块位图一位对应一个内存块,这一层的相邻内存块都空闲时无法向上回收,存在相邻两块空闲的情况,故需要1位对应一块
if(num == 0)
{
num = 1;
}
save_adr -= num*4; //每一个32位位图4个字节
save_adr &= ~(4-1); //四字节对齐
acoral_mem_ctrl->bitmap[i] = (unsigned int *)save_adr;
acoral_mem_ctrl->num[i] = num;
save_adr -= num*4;
save_adr &= ~(4-1); //四字节对齐
acoral_mem_ctrl->free_list[i] = (int *)save_adr;
for(k=0; k<num; k++)
{
acoral_mem_ctrl->bitmap[i][k] = 0;
acoral_mem_ctrl->free_list[i][k] = -1;
}
acoral_mem_ctrl->free_cur[i] = -1;
// 如果剩余内存大小不够形成现在的level,对伙伴系统层数m进行调整,如果将刚才描述的数据结构分配出去后,最初的层数比当前系统的层数少1,则减少层数。例如,基本内存块为1KB,而初始的堆内存大小为1.024MB,则可知最开始算的层数为11。但是将内存管理需要的控制块的内存分配后,可能只剩下999KB,只需要10层即可管理,所以对层数进行调整
if(save_adr <= (start_adr + (resize_size >> 1)))
adjust_level--;
if(adject_level > LEVEL)
level = LEVEL;
//初始化内存控制块
acoral_mem_ctrl->level = level;
acoral_mem_ctrl->start_adr = start_adr;
num = (save_adr - start_adr) >> BLOCK_SHIFT;
acoral_mem_ctrl->end_adr = start_adr + (num << BLOCK_SHIFT);
acoral_mem_ctrl->block_num = num;
acoral_mem_ctrl->free_num = num;
acoral_mem_ctrl->block_size = BASIC_BLOCK_SIZE;
i = 0;
max_num = (1 << level) - 1;
o_num = 0;
//设置第level-1层(即最高层)空闲位图块链表头的值,可见空闲位图块指向该层的0号空闲内存块free_list。
if (num > 0)
{ // 有内存块,则最大内存块层的free_cur为0
acoral_mem_ctrl->free_cur[level - 1] = 0;
}
else
{ // 无内存块,则最大内存块层的free_cur为-1
acoral_mem_ctrl->free_cur[level - 1] = -1;
}
//有了前面的准备工作,接下来开始把实际可用的内存分配到各个逻辑层,分配的原则是:首先将内存都尽量分配给高层,直到剩下的内存不够这一层的一个内存块大小,再依次分配给低层。
//首先考虑最高层:(level-1)层,如果该层的内存块数是32的倍数,整块内存优先分给最大内存块层,计算当前可分配内存容量能否直接形成一个最大内存块层的32位图
while (num >= max_num * 32)
{
acoral_mem_ctrl->bitmap[level - 1][i] = -1;
;
acoral_mem_ctrl->free_list[level - 1][i] = i + 1;
num -= max_num * 32;
o_num += max_num * 32;
i++;
}
if (num == 0)
{ // 所有块正好分配到最大内存块层的32位图
acoral_mem_ctrl->free_list[level - 1][i - 1] = -1;
}
// 计算当前可分配内存是否还能形成最大内存块层的一块
while (num >= max_num)
{
index = (o_num >> level) - 1;
acoral_set_bit(index, acoral_mem_ctrl->bitmap[level - 1]);
num -= max_num;
o_num += max_num;
}
acoral_mem_ctrl->free_list[level - 1][i] = -1;
// 接下来的每层初始化
while (--level > 0)
{
index = o_num >> level;
if (num == 0)
break;
cur = index / 32;
max_num = (1 << level) - 1; // 每层的内存块大小
if (num >= max_num)
{
acoral_mem_blocks[BLOCK_INDEX(o_num)].level = -1;
acoral_set_bit(index, acoral_mem_ctrl->bitmap[level - 1]);
acoral_mem_ctrl->free_list[level - 1][cur] = -1;
acoral_mem_ctrl->free_cur[level - 1] = cur;
o_num += max_num;
num -= max_num;
}
}
return 0;
}
如起始地址:0x7 = 0111 加3 = 1010 & (1100) = 1000 = 0x8
结束地址:0x7 = 0111 & (1100) = 0100 = 0x4