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。