Next Fit算法
首先为了作一个对比,使用了一个来自于《C程序设计语言》上面的malloc版本来进行比对。
这个简单版本的思想是使用一个循环单链表来进行空闲块的维护,base变量表示该链表的头,freep表示上次操作的(malloc或者free)的空闲块。
分配一定大小的块的时候,从freep开始找,知道找到一个空闲块其大小大于等于请求块的大小。如果大小刚好,则直接分配出去,否则从尾部开始分配请求大小的块,剩余的大小就变成新的空闲块,这样做的好处是不用维护一个指向freep前一个空闲块的指针,而只需要改变size值就可以。这个算法在操作系统的术语里面叫做Next Fit。
这个算法具有一定的随机性的思想,所以总体来说块的分配会比较平均。
以下是malloc.h的内容:
#ifndef _MALLOC_H_
#define _MALLOC_H_
typedef long Align;
union header {
struct {
union header *ptr;
unsigned size;
} s;
Align x; /* used for alignment */
};
typedef union header Header;
void *malloc(unsigned);
void free(void *);
#endif
下面是malloc和free的函数定义:
#include "malloc.h"
#include <stddef.h>
static Header base; /* the beginning of the free block list */
static Header *freep = NULL; /* pointer to the block we just dealing with */
static Header *morecore(unsigned nu);
void *malloc(unsigned nbytes)
{
Header *p, *prevp;
unsigned nunits;
nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;
if ((prevp = freep) == NULL)
{
base.s.ptr = freep = prevp = &base;
base.s.size = 0;
}
for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr)
{
if (p->s.size >= nunits)
{
if (p->s.size == nunits)
prevp->s.ptr = p->s.ptr;
else
{
p->s.size -= nunits;
p += p->s.size;
p->s.size = nunits;
}
freep = prevp;
return (void *)(p + 1);
}
if (p == freep)
if ((p = morecore(nunits)) == NULL)
return NULL;
}
return 0;
}
void free(void *ap)
{
Header *bp, *p;
bp = (Header *)ap - 1;
for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
if (p >= p->s.ptr && (bp > p || bp < p->s.ptr))
break;
if (bp + bp->s.size == p->s.ptr)
{
bp->s.size += p->s.ptr->s.size;
bp->s.ptr = p->s.ptr->s.ptr;
}
else
{
bp->s.ptr = p->s.ptr;
}
if (p + p->s.size == bp)
{
p->s.size += bp->s.size;
p->s.ptr = bp->s.ptr;
}
else
p->s.ptr = bp;
freep = p;
}
#define NALLOC 1024
static Header *morecore(unsigned nu)
{
char *cp, *sbrk(int);
Header *up;
if (nu < NALLOC)
nu = NALLOC;
cp = sbrk(nu * sizeof(Header));
if (cp == (char *) -1)
return NULL;
up = (Header *) cp;
up->s.size = nu;
free((void *)(up + 1));
return freep;
}
Buddy Algorithm(伙伴算法)
就如Reference里面的算法描述,伙伴算法的核心思想是把块的大小都定好,一般是2的幂的大小。
在这里我们会有一个最大的幂值,称为u,也就是最大块就是2^u那么大。
有一个最小幂值,称为l,最小块的大小就是2^l那么大。
然后不同大小的块使用不同的链表来管理,总的是一个free_area的数组来存储这些链表的表头。其中数组free_area里面的下标表示的就是这个链表对应的块的大小的幂数。
然后在分配空闲块的时候首先从最小的大于申请大小的块开始查找。如果这个申请块的幂数是7那么就从free_area[7]开始查找。如果这个链表为空,则往大的链表开始搜索,如果搜索到了的话,将大块分割成两个小块,其中一块用来分配,另外一块用来存在小的块的链表里面。
这里我的实现是使用了双向链表来实现。
而在free的过程中,就查看这个释放块的相邻块是否也是空闲,这里相邻块称为伙伴(buddy)。如果伙伴也是在空闲块的块的话,那么就将这两个块合并,这个过程递归,不能再合并为止。
下面是buddy.h里面关于函数的定义:
#ifndef _BUDDY_H_
#define _BUDDY_H_
void *buddy_alloc(unsigned int);
void buddy_free(void *);
void buddy_showblocks();
#endif
下面相关的数据结构和函数定义:
#include "buddy.h"
#include <stdio.h>
#include <stddef.h>
/* u = 15, l = 6 */
#define BUF_SIZE 32768
#define MIN_ORDER 6
#define MAX_ORDER 15
typedef long Align;
union header {
struct {
union header *next;
union header *prev;
unsigned size;
} s;
Align x; /* used for alignment */
};
typedef union header Header;
static char buffer[BUF_SIZE];
static Header *free_area[MAX_ORDER + 1] = {NULL};
static Header *free_buf = NULL;
unsigned int round2power(unsigned int num)
{
unsigned int init = 1 << MIN_ORDER;
unsigned int count = MIN_ORDER;
while (init < num)
{
init <<= 1;
count++;
}
return count <= MAX_ORDER? count : MAX_ORDER;
}
static void insert(Header **listp, Header *block)
{
Header *prev = NULL, *current = *listp;
if (*listp == NULL)
{
*listp = block;
block->s.prev = NULL;
block->s.next = NULL;
return;
}
while (current != NULL)
{
if (prev == NULL && block < current)
{
*listp = block;
block->s.next = current;
current->s.prev = block;
block->s.prev = NULL;
return;
}
if(block < current)
{
prev->s.next = block;
block->s.prev = prev;
block->s.next = current;
current->s.prev = block;
return;
}
prev = current;
current = current->s.next;
}
if (prev != NULL)
{
prev->s.next = block;
block->s.prev = prev;
block->s.next = NULL;
return;
}
}
static void delete(Header **listp, Header *block)
{
if (*listp == NULL)
return;
/* If the block is the head of the list */
if (*listp == block)
{
*listp = block->s.next;
}
if (block->s.prev != NULL)
{
block->s.prev->s.next = block->s.next;
}
if (block->s.next != NULL)
{
block->s.next->s.prev = block->s.prev;
}
return;
}
static int continous(Header *p1, Header *p2)
{
char *ptr1 = (char *) p1;
char *ptr2 = (char *) p2;
return (ptr1 + p1->s.size) == ptr2;
}
static Header *combine(Header *p1, Header *p2)
{
if (p1->s.size != p2->s.size)
return NULL;
p1->s.size <<= 1;
return p1;
}
void *buddy_alloc(unsigned int nbytes)
{
Header *p;
unsigned nunits, idx;
int i;
idx = round2power(nbytes + sizeof(Header));
/* Initilize the header block */
if (free_buf == NULL)
{
free_buf = free_area[MAX_ORDER] = (Header *) buffer;
free_area[MAX_ORDER]->s.next = NULL;
free_area[MAX_ORDER]->s.prev = NULL;
free_area[MAX_ORDER]->s.size = 1 << MAX_ORDER;
}
for (i = idx; i <= MAX_ORDER; i++)
{
if (free_area[i] != NULL)
{
unsigned int j = i - 1;
unsigned int size = 1 << j;
Header *firstp, *secondp;
for (firstp = free_area[i]; j >= idx; j--, size >>= 1)
{
secondp = (Header *)((char *)firstp + size);
secondp->s.next = NULL;
secondp->s.prev = NULL;
secondp->s.size = size;
insert(&free_area[j], secondp);
}
free_area[i] = firstp->s.next;
p = firstp;
p->s.next = NULL;
p->s.prev = NULL;
p->s.size = 1 << idx;
return p + 1;
}
}
return NULL;
}
void buddy_free(void *bp)
{
Header *prev, *p, *next;
unsigned int idx;
p = (Header *) bp - 1;
idx = round2power(p->s.size);
insert(&free_area[idx], p);
prev = p->s.prev;
next = p->s.next;
/* check whether the buddies can be combined into the larger one */
if (prev != NULL)
{
if (continous(prev, p))
{
delete(&free_area[idx], p);
delete(&free_area[idx], prev);
prev = combine(prev, p);
buddy_free((void *) (prev + 1));
return;
}
}
if (next != NULL)
{
if (continous(p, next))
{
delete(&free_area[idx], p);
delete(&free_area[idx], next);
next = combine(p, next);
buddy_free((void *) (next + 1));
return;
}
}
}
void buddy_showblocks_test();
void buddy_showblocks()
{
buddy_showblocks_test();
unsigned int size = 1 << MIN_ORDER;
int i;
unsigned int bsize;
Header *bp;
char *ptr = buffer;
char *buf_end = &buffer[BUF_SIZE - 1] + 1;
for (bp = (Header *) ptr; ptr < buf_end; ptr += size, bp = (Header *)ptr)
{
bsize = bp->s.size;
unsigned int tmpsize = bsize / size;
int idx = round2power(bsize);
Header *tp = free_area[idx];
while (tp != NULL)
if (tp == bp)
break;
else
tp = tp->s.next;
if (tp != NULL)
{
for (i = 0; i < tmpsize; i++)
{
printf("---/n");
}
}
else
{
for (i = 0; i < tmpsize; i++)
{
printf("***/n");
}
}
}
}
void buddy_showblocks_test()
{
int i;
for (i = MIN_ORDER; i <= MAX_ORDER; i++)
{
if (free_area[i] != NULL)
{
Header *tmp = free_area[i];
int j;
for (j = 0; tmp != NULL; j++, tmp = tmp->s.next)
;
printf("%d has blocks: %d/n", i, j);
}
else
printf("%d is empty./n", i);
}
}
可以看到伙伴算法对于零散块的处理是比较好的。伙伴算法相对于Next Fit算法的好处是减少了External Fragmentation(外部碎块,也就是在分配块之间的洞),但是增加了Internal Fragmentation(内部碎块,也就是在申请块内部有一部分是没有被用到的)。
经过测试,使用了100次随机从20到120的随机大小块分配,然后再随机的从这些分配块里面释放。
得到的结果是Next Fit的碎片数目是16,而Buddy的数目是11。
从这个数字多少可以感受到对于碎片的处理,Buddy的确是优于Next Fit。