MIT-6.s081-OS lab alloc: Allocator for xv6

本实验并不需要实现一个allocator 主要做这两件事:

1.从实验指导书中我们了解到 在一开始的时候 xv6打开文件数是有限制的 最多为NFILE 因为他没有使用动态内存分配,而是静态定义了数组,所以首先要我们做的就是,让xv6使用buddy allocator来动态分配文件相关的数据结构,这一部分我们要修改的文件是file.c

2.这一部分我们需要优化给出的buddy allocator,在原始版本中,为每个block准备了一个alloc bit,为0,表示没有分配,为1,表示分配了,因为buddy allocator的特点,我们为每一对block使用一个bit,等于b1_is_free_xor_b2_is_free,这样下来,最终大概能节约1mb的空间

首先是第一部分:

首先注释掉静态定义的数组

struct {
  struct spinlock lock;
  // struct file file[NFILE];
} ftable;
// Allocate a file structure.
struct file*
filealloc(void)
{
  struct file *f;

  acquire(&ftable.lock);
  f=bd_malloc(sizeof(struct file));
  if(f){
    f->ref=1;
    release(&ftable.lock);
    return f;
  }  
  // for(f = ftable.file; f < ftable.file + NFILE; f++){
  //   if(f->ref == 0){
  //     f->ref = 1;
  //     release(&ftable.lock);
  //     return f;
  //   }
  // }
  release(&ftable.lock);
  return 0;
}
// Close file f.  (Decrement ref count, close when reaches 0.)
void
fileclose(struct file *f)
{
  struct file ff;

  acquire(&ftable.lock);
  if(f->ref < 1)
    panic("fileclose");
  if(--f->ref > 0){
    release(&ftable.lock);
    return;
  }
  ff = *f;
  f->ref = 0;
  f->type = FD_NONE;
  bd_free(f);
  release(&ftable.lock);

  if(ff.type == FD_PIPE){
    pipeclose(ff.pipe, ff.writable);
  } else if(ff.type == FD_INODE || ff.type == FD_DEVICE){
    begin_op(ff.ip->dev);
    iput(ff.ip);
    end_op(ff.ip->dev);
  }
}

然后修改fileclose和filealloc,前者中只需要在release前bd_free掉file

后者只需要在filealloc中,注释掉原来静态定义时用来初始化file的循环,然后用bd_malloc为file申请内存,并且初始化(ref为1),然后release并返回即可

这样就可以通过filetest了

TODO:

但是静态定义时 是定义的NFILE个,为什么现在malloc的时候只需要malloc一个呢?而不是SIZEOF之后乘以一个size?

第二部分

首先要搞清楚 一个bit 怎么保存两个块的分配状态:实际上现在只能知道 两个buddy状态是否相同 不能单独判断其中的一个是否已分配

但是 阅读buddy allocator的源码后可以发现:即使用一个bit,也能实现

首先我们要理解 buddy allocator:

通过阅读bd_init函数,可以发现,buddy allocator管理的一片内存布局是这样的:

绿色部分左边是所谓meta data,即保存内存块相关信息的数据,比如alloc保存块是否已经分配的信息,split保存块是否已经split的信息

而绿色部分 是真正可用来分配的区域,因此,bd_init中将metadata部分标记为了allocated(标记为allocated的还有unavailable,这一部分实际上并不存在于管理的这一片内存区域,实际上是因为管理的一片内存区域可能不是恰好等于2^n,那么为了覆盖到整块内存,最终选取的n会导致2^n > mem ,因此就有了这一部分unavailable)

然后需要关注的是init_free函数:

// Initialize the free lists for each size k.  For each size k, there

// are only two pairs that may have a buddy that should be on free list:

// bd_left and bd_right.

这里的注释说:为不同的size初始化free list ,并且 每个size,只有两对block,可能有一个buddy是free的,即bd_left,bd_right

需要指出的是,这里的bd_free和bd_end就是上图中绿色部分的左右边界

那这段注释为什么这么说?首先应该注意到,在bd_init中,我们已经把alloc数据memset为全0了,也就是说全部为free,除了两块区域:meta data和unavailable,他们被标记为allocated

而阅读源码可知 每一个块的buddy 和他的block index 是相邻的 : 对于在上述两块区域里的块(不在边界上),他们的buddy也被标记为allocated,而唯一可能有free buddy的,就是边界上的块,即bd_left,bd_end

然后再看bd_initfree函数,对于每一个size k ( 除了maxsize),我们计算出了bd_left对应块的下一个块的index,以及bd_right的对应块的index,然后调用bd_init_pair,而这个函数的作用是,把一对块中free的那一个,加入到对应size 的 free list中

现在来考虑如何用一个bit优化掉原来的两个bit,从而节约内存

首先当然是只分配一半的bit

  // initialize free list and allocate the alloc array for each size k
  for (int k = 0; k < nsizes; k++) {
    lst_init(&bd_sizes[k].free);
    sz = sizeof(char)* ROUNDUP(NBLK(k), 8)/8;
    sz/=2;
    bd_sizes[k].alloc = p;
    memset(bd_sizes[k].alloc, 0, sz);
    p += sz;
  }

然后就需要修改一切与alloc数组相关的部分:

优化前,我们通过把每个块的bit设为0/1表示free/allocated,现在我们把bit设为0/1 表示两个buddy的分配状态是否相同,首先我们需要一些操作bit的函数,因为现在alloc和split不能共用一套函数了,前者是每两个block一个bit,后者一个block一个bit

// toggle bit at position index in array
void bit_toggle(char *array,int index){
  index/=2;
  char b = array[index/8];
  char m = (1 << (index % 8));
  array[index/8] = (b ^ m);
}

// return 1 if the bit at position index in array is 1
void bit_get(char *array,int index){
  index/=2;
  char b = array[index/8];
  char m = (1 << (index % 8));
  return (b & m) == m;
}

这里为什么是/2,之后%8, 而不是直接%16?

举个例子:比如0,1,2,3,4... 8和9是一对,那么他们应该映射到一个bit,如果直接%16,得到的是8和9,不同,如果/2,再%8,那么得到的就是同一个bit

然后就用上述函数对原代码进行一系列修改:

// Mark memory from [start, stop), starting at size 0, as allocated. 
void
bd_mark(void *start, void *stop)
{
  int bi, bj;

  if (((uint64) start % LEAF_SIZE != 0) || ((uint64) stop % LEAF_SIZE != 0))
    panic("bd_mark");

  for (int k = 0; k < nsizes; k++) {
    bi = blk_index(k, start);
    bj = blk_index_next(k, stop);
    for(; bi < bj; bi++) {
      if(k > 0) {
        // if a block is allocated at size k, mark it as split too.
        bit_set(bd_sizes[k].split, bi);
      }
      // bit_set(bd_sizes[k].alloc, bi);
      bit_toggle(bd_sizes[k].alloc,bi);
    }
  }
}

在修改bd_initfree_pair时遇到一个问题:这里需要判断,哪一个是free的,就处理哪一个,但是如前所说,xor的结果只能说明两个是否相同,不能说明单独一个的状态

但是分析过后,发现可以通过其他方式判断:这个函数是被bd_initfree调用的,用来把一对中free的一个block插入到free list,而如前所述,这里只关注边界处的block,而绿色部分之外的两部分都是allocated,所以,我们可以通过该块的地址判断,只要他的地址在绿色部分之内,那么他就是free的(如果xor的结果为1,也就是分配状态不相同)

// return 1 if addr is in range (left,right)
int addr_in_range(void *addr,void *left,void *right){
  return (addr>=left)&&(addr<right);
}

// If a block is marked as allocated and the buddy is free, put the
// buddy on the free list at size k.
int
bd_initfree_pair(int k, int bi,void *left,void *right) {
  int buddy = (bi % 2 == 0) ? bi+1 : bi-1;
  int free = 0;
  // if(bit_isset(bd_sizes[k].alloc, bi) !=  bit_isset(bd_sizes[k].alloc, buddy)) {
  if(bit_get(bd_sizes[k].alloc,bi)){
    // one of the pair is free
    free = BLK_SIZE(k);
    // debug
    // printf("bi (%d,%p,%d)\tbuddy(%d,%p,%d)\n",bi, addr(k, bi),addr_in_range(addr(k,bi),left,right),buddy, addr(k, buddy),addr_in_range(addr(k,buddy),left,right));
    // FIXIT why must check buddy first?
    if(addr_in_range(addr(k,buddy),left,right)){
      lst_push(&bd_sizes[k].free, addr(k, buddy)); 
    }else{
      lst_push(&bd_sizes[k].free, addr(k, bi)); 
    }
    // if(bit_isset(bd_sizes[k].alloc, bi))
    //   lst_push(&bd_sizes[k].free, addr(k, buddy));   // put buddy on free list
    // else
    //   lst_push(&bd_sizes[k].free, addr(k, bi));      // put bi on free list
  }
  return free;
}

注:如上,这里有一个疑问,为什么当xor结果为1,即两者状态不同时,必须先判断buddy的地址,而不是bi的地址,否则就会出错

我debug了一下,发现可能出现两者地址都在range内的情况,不解..

bi (131121,0x000000008022a310,1) buddy(131120,0x000000008022a300,0)
bi (65561,0x000000008022a320,1) buddy(65560,0x000000008022a300,0)
bi (32781,0x000000008022a340,1) buddy(32780,0x000000008022a300,0)
bi (16391,0x000000008022a380,1) buddy(16390,0x000000008022a300,0)
bi (2049,0x000000008022a400,1)  buddy(2048,0x000000008022a000,0)
bi (1025,0x000000008022a800,1)  buddy(1024,0x000000008022a000,0)
bi (513,0x000000008022b000,1)   buddy(512,0x000000008022a000,0)
bi (257,0x000000008022c000,1)   buddy(256,0x000000008022a000,0)
bi (16363,0x0000000088000000,0) buddy(16362,0x0000000087ffe000,1)
bi (129,0x000000008022e000,1)   buddy(128,0x000000008022a000,0)
bi (8181,0x0000000087ffe000,1)  buddy(8180,0x0000000087ffa000,1)
bi (65,0x0000000080232000,1)    buddy(64,0x000000008022a000,0)
bi (33,0x000000008023a000,1)    buddy(32,0x000000008022a000,0)
bi (2045,0x0000000087ffa000,1)  buddy(2044,0x0000000087fea000,1)
bi (17,0x000000008024a000,1)    buddy(16,0x000000008022a000,0)
bi (9,0x000000008026a000,1)     buddy(8,0x000000008022a000,0)
bi (511,0x0000000087fea000,1)   buddy(510,0x0000000087faa000,1)
bi (5,0x00000000802aa000,1)     buddy(4,0x000000008022a000,0)
bi (255,0x0000000087faa000,1)   buddy(254,0x0000000087f2a000,1)
bi (3,0x000000008032a000,1)     buddy(2,0x000000008022a000,0)
bi (127,0x0000000087f2a000,1)   buddy(126,0x0000000087e2a000,1)
bi (63,0x0000000087e2a000,1)    buddy(62,0x0000000087c2a000,1)
bi (1,0x000000008042a000,1)     buddy(0,0x000000008002a000,0)
bi (31,0x0000000087c2a000,1)    buddy(30,0x000000008782a000,1)
bi (1,0x000000008082a000,1)     buddy(0,0x000000008002a000,0)
bi (15,0x000000008782a000,1)    buddy(14,0x000000008702a000,1)
bi (1,0x000000008102a000,1)     buddy(0,0x000000008002a000,0)
bi (7,0x000000008702a000,1)     buddy(6,0x000000008602a000,1)
bi (1,0x000000008202a000,1)     buddy(0,0x000000008002a000,0)
bi (3,0x000000008602a000,1)     buddy(2,0x000000008402a000,1)

还有一些类似的修改

然后满分通过

alloctest: OK (7.7s) 
alloctest: OK (6.2s) 
usertests: OK (142.9s) 
    (Old xv6.out.usertests failure log removed)
Score: 100/100

小结:本实验主要是阅读源码的基础上修改,其中body allocator我在cs:app上看到过介绍,之前做malloc lab时做的是更简单的类型 explicit list好像,buddy要稍微复杂一些

猜你喜欢

转载自blog.csdn.net/RedemptionC/article/details/107368186