看数据结构写代码(50)伙伴系统

伙伴系统是一种动态内存管理方式,仅分配2的幂次方大小的空间,回收时合并伙伴空间。本文介绍了如何初始化、分配和回收内存,以及伙伴空间的查找与合并规则。通过示例解释了如何在64个单位的内存中分配和回收不同大小的空间,并提供了作者自创的实现代码。
摘要由CSDN通过智能技术生成

伙伴系统 是一种 只 可以 分配 2的 幂次方 个 空间的 ,回收 内存 时 只 合并 “伙伴空间” 的一种 动态内存管理方式。

例如 一个 空间 大小 为 64 的 内存,伙伴 系统 为 这 64 的内存  建立 一组 双向循环 链表,分别 管理着  2的 0 次方,2的1 次方幂,2的 2 次方幂。。。2的6次方幂的 可用空间。

即使 我们 只想分配 一个 大小 为3的 空间,系统 却 只能 返回 一个 内存 大小 为 4(2的2次方)的 一个空间。

系统 在 初始化的 时候 ,并不是 为 每一个 链表 都 放入 一些 可用空间 。而是 在 2的 6次方幂 插入 一个 空间节点,大小 为64

当我们 想 要 分配 内存的时候,会 查找 最近 内存大小的 链表,如果 链表 有 可用空间,取 第一个 空间。否则 顺次 查找 可用空间。

假设 我们 要在 这个 64 的 空间 里,分配 一个 大小 为 3 的内存,系统 怎么 分配呢? 

系统 会 从 第一个链表 一直 找,直到 找到 2的 6次方的 链表。系统 会 将 前 4个 空间 分配 给用户。可是 其余的 60个空间 ,不能 再 插入到 2的 6次方 的链表中了。只能 插入到

比 它小的链表中去。

具体的 分配 方式 如下:4    4    8   16   32 。

 前4个 分给用户 使用,后 面的 4 8 16 32  依次 插入 到 2的 2次方幂,2的 3次方,4次方,5次方幂的链表中去。

这 有一个 规则:

1. 用户 空间 的 邻接空间 总是 被 拆分成  一个 跟 用户 空间 大小 一样的 空间。这个 空间 就是 上面 所有的 “伙伴空间”。

2. 再 下一个空间 依次 是 用户空间的 2倍,4倍,8倍,。。。直到 被拆分空间 一半。 例子的 拆分空间 为 64,它的一半 为 32。

所以 可以 算出:64 是 如何 被 拆分的: 4(用户空间) 4(伙伴空间) 8   16  32 


当我们 回收空间时,首先 会 查看 这个空间的 伙伴空间 是否 为 空闲。如果空闲 会 合并,并从 链表中 删除 伙伴空间。。然后 会 再次 查看 合并后的 空间。直至 无法 合并。如果 不空闲,插入到 合适的 链表中去。

伙伴空间 就是 上面 所说的 “4” ,必须 跟 回收空间 空间大小一样 ,而且 是 从 同一个 大块 内存 分配 出去的,

有一个公式可以求出 伙伴空间:



例如 释放 上面 的 空间, 会查找 伙伴空间 ,伙伴空间 空闲,合并空间 成为 一个 大小 为8 的空间,并从 2 的 2 次方 链表中 删除 伙伴空间,然后 继续 查找。 继续删除。。最终 会 合并 成 一个 在 2的 6次幂链表中的 一个 空间。 和 初始的情况一样。

下面的 代码 我 没有 按照 书上的代码来写,按照 自己的思路写的,(书上的代码看的我有点 糊涂)。如有误,望指出。


代码如下:

// buddyAlloc.cpp : 定义控制台应用程序的入口点。
//伙伴系统

#include "stdafx.h"
#include <cstdlib>
#include <cmath>

#define MAX_SIZE	64//最大空间
#define LIST_LEN	7//表头数组长度
#define MAX_INDEX	6//表头数组最大下标
struct BuddyNode{
	BuddyNode * preLink;
	BuddyNode * nextLink;
	int k;//只能分配2 的k次幂
	int tag;//0 空闲, 1 占用
};

typedef struct BuddyHead{
	BuddyNode * head;
	int nodeSize;
}FreeList[LIST_LEN];

static BuddyNode * freeSpace = NULL;//为了释放内存
static BuddyNode * userSpace[MAX_SIZE] = {NULL};
static int userCount = 0;

void initFreeList(FreeList list){
	for (int i = 0; i < LIST_LEN; i++){
		list[i].head = NULL;
		list[i].nodeSize = (int)pow(2.0,i);
	}
	//分配最大空间
	BuddyNode * p = (BuddyNode*)malloc(sizeof(BuddyNode) * MAX_SIZE);
	if (p != NULL){
		p->k = MAX_INDEX;
		p->tag = 0;
		p->nextLink = p->preLink = p;//双向循环链表
	}
	freeSpace = list[MAX_INDEX].head = p;
}

void destoryFreeList(FreeList list){
	for (int i = 0; i < LIST_LEN; i++){
		list[i].head = NULL;
	}
	free(freeSpace);
}

BuddyNode * buddyAlloc(FreeList list,int size){
	bool isFirst = true;
	int k = -1;//分配空间的k值
	int needK = -1;//需要分配空间的k值.
	//查找第一个满足条件的空间.
	for (int i = 0; i < LIST_LEN; i++){
		BuddyHead head = list[i];
		if (head.nodeSize >= size){
			if (isFirst){
				needK = i;
				isFirst = false;
			}
			if (head.head != NULL){//找到可分配的空间了
				k = i;
				break;
			}
		}
	}
	BuddyNode * freeNode = NULL;
	if (k == -1){
		return NULL;//找不到满足条件的空间了..
	}
	else{
		//设置这个空间 被占用,并且 更换 表头元素..
		freeNode = list[k].head;
		freeNode->k = needK;
		freeNode->tag = 1;//设置 成 已占用..
		list[k].head = freeNode->nextLink;//重新设置表头..
		if (freeNode->preLink == freeNode->nextLink){//删除这个空间后,成为空表.
			list[k].head = NULL;
		}
		else{//删除这个节点.
			freeNode->preLink->nextLink = freeNode->nextLink;
			freeNode->nextLink->preLink = freeNode->preLink;
		}
		//剩余空间 依次 插在 needK 和 k-1 表里.
		for (int i = needK; i < k ; i++){//从高位开始分配..
			int index = (int)pow(2.0,i);
			BuddyNode * p = freeNode + index;
			p->k = i;
			p->tag = 0;
			BuddyNode * head = list[i].head;
			if (head != NULL){
				p->preLink = head->preLink;
				p->nextLink = head;
				p->preLink->nextLink = head->preLink = p;
			}
			else{
				p->preLink = p->nextLink = p;
			}
			list[i].head = p;//重新设置表头..
		}
	}
	userSpace[userCount++] = freeNode;
	return freeNode;
}

void userSpaceFree(BuddyNode * node){
	for (int i = 0; i < userCount; i++){
		if (userSpace[i] == node){
			userSpace[i] = NULL;
		}
	}
}

//伙伴算法 回收...
void buddyReclaim(FreeList list,BuddyNode * node){
	userSpaceFree(node);
	while (node->k < MAX_INDEX){//循环查找伙伴,k值达到 MAX_INDEX 不需要 寻找...
		int sub = node - freeSpace;//计算出 具体坐标位置
		BuddyNode * buddy = NULL;
		int i = (int)pow(2.0,node->k + 1);
		bool isNext = true;//伙伴是不是在后
		if(sub % i == 0){//伙伴在后
			buddy = node + i/2;
		}
		else{//伙伴在前.
			buddy = node - i/2;
			isNext = false;
		}
		if (buddy->tag == 0 ){//伙伴空闲,合并
			//首先从列表里释放 buddy
			if (buddy->preLink == buddy->nextLink){//表中 只有一个节点,释放后,成为空表
				list[buddy->k].head = NULL;
			}
			else{//删除节点
				buddy->preLink->nextLink = buddy->nextLink;
				buddy->nextLink->preLink = buddy->preLink;
				list[buddy->k].head = buddy->nextLink;
			}
			if (isNext == false){
				node = buddy;
			}
			node->k ++;
		}
		else{//伙伴空间被占用..
			break;
		}
	}
	BuddyNode * head = list[node->k].head;
	node->tag = 0;
	if (head == NULL){//表头为空
			node->preLink = node->nextLink = node;
	}
	else{
		node->nextLink = head;
		node->preLink = head->preLink;
		node->preLink->nextLink = head->preLink = node;
	}
	list[node->k].head = node;
}

void printList(FreeList list){
	printf("------------------------打印伙伴列表-------------------\n");
	for (int i = 0; i < LIST_LEN; i++){
		BuddyNode * head = list[i].head;
		if (head){
			printf("首地址空间为:%0x,表的前驱:%0x,后继:%0x,表空间大小是2的%d次方\n",head,head->preLink,head->nextLink,head->k);
		}
	}
	printf("------------------------用户空间-------------------\n");
	for (int i = 0; i < userCount; i++){
		BuddyNode * us = userSpace[i];
		if (us != NULL){
			printf("首地址空间为:%0x,表空间大小是2的%d次方\n",us,us->k);
		}
	}
	printf("\n");
}



int _tmain(int argc, _TCHAR* argv[])
{
	FreeList list;
	initFreeList(list);
	printList(list);
	printf("---------------分配一个1空间之后--------------\n");
	BuddyNode * s1_1 = buddyAlloc(list,1);
	printList(list);
	printf("---------------分配一个1空间之后--------------\n");
	BuddyNode * s1_2 = buddyAlloc(list,1);
	printList(list);
	printf("---------------分配一个29空间之后--------------\n");
	BuddyNode * s29 = buddyAlloc(list,29);
	printList(list);
	printf("---------------释放一个1空间之后--------------\n");
	buddyReclaim(list,s1_2);
	printList(list);
	printf("---------------释放一个1空间之后--------------\n");
	buddyReclaim(list,s1_1);
	printList(list);
	destoryFreeList(list);
	return 0;
}

完整代码工程文件网盘地址:点击打开链接


运行截图:






假设系统的可利用空间容量为2m个字,则系统开始运行时,整个内存区是一个大小为2m的空闲分区。在系统运行过程中,由于不断的划分,可能会形成若干个不连续的空闲分区,将这些空闲分区根据分区的大小进行分类,对于每一类具有相同大小的所有空闲分区,单独设立一个空闲分区双向链表。这样,不同大小的空闲分区形成了k(0≤k≤m)个空闲分区链表。 当需要为进程分配一个长度为n的存储空间时,首先计算一个i值,使2i-1<n≤2i,然后在空闲分区大小为2i的空闲分区链表中查找。若找到,即把该空闲分区分配给进程。否则,表明长度为2i的空闲分区已经耗尽,则在分区大小为2i+1的空闲分区链表中寻找。若存在2i+1的一个空闲分区,则把该空闲分区分为相等的连个分区,这两个分区称为一对伙伴,其中的一个分区用于分配,而把另一个加入分区大小为2i的空闲分区链表中。若大小为2i+1的空闲分区不存在,则需要查找大小为2i+2的空闲分区,若找到则对其进行两次分割:第一次,将其分割为大小为2i+1的两个分区,一个用于分配,一个加入到大小为2i+1空闲分区链表中;第二次,将第一次用于分配的空闲分区分割为2i的两个分区,一个用于分配,一个加入到大小为2i空闲分区链表中。若仍然找不到,则继续查找大小为2i+3的空闲分区,以此类推。由此可见,在最坏的情况下,可能需要对2k的空闲分区进行k次分割才能得到所需分区。 与一次分配可能要进行多次分割一样,一次回收也可能要进行多次合并,如回收大小为2i的空闲分区时,若事先已存在2i的空闲分区时,则应将其与伙伴分区合并为大小为2i+1的空闲分区,若事先已存在2i+1的空闲分区时,又应继续与其伙伴分区合并为大小为2i+2的空闲分区,依此类推。 2.2 伙伴系统的需求 根据伙伴系统算法的思想,我们组对本系统的功能划分为3种: ⑴ 根据伙伴系统算法分配内存 ⑵ 根据伙伴系统算法回收内存 ⑶ 实时查看内存使用的情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值