Mit6.s081 lab8

基本思想是为每个CPU维护一个空闲列表,每个列表都有自己的锁。因为每个CPU将在不同的列表上运行,不同CPU上的分配和释放可以并行运行。主要的挑战将是处理一个CPU的空闲列表为空,而另一个CPU的列表有空闲内存的情况;在这种情况下,一个CPU必须“窃取”另一个CPU空闲列表的一部分。窃取可能会引入锁争用,但这种情况希望不会经常发生。

Memory allocator

实验要求:实现一个per CPU freelist,以减小各个进程同时调用kallockfree造成的对kmem.lock锁的竞争。可以调用cpuid()函数来获取当前进程运行的CPU ID,但是要在调用前加上push_off以关闭中断。

kernel/kalloc.c中,修改kmem结构体为数组形式

struct {
  struct spinlock lock;
  struct run *freelist;
} kmem[NCPU];

kinit()要循环初始化每一个kmem的锁

void
kinit()
{
  for (int i = 0; i < NCPU; i++) {
    initlock(&kmem[i].lock, "kmem");
  }
  freerange(end, (void*)PHYSTOP);
}

kfree将释放出来的freelist节点返回给调用kfree的CPU

void
kfree(void *pa)
{
  struct run *r;

  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

  // Fill with junk to catch dangling refs.
  memset(pa, 1, PGSIZE);

  r = (struct run*)pa;

  push_off();
  int ncpu = cpuid();

  acquire(&kmem[ncpu].lock);
  r->next = kmem[ncpu].freelist;
  kmem[ncpu].freelist = r;
  release(&kmem[ncpu].lock);
  pop_off();
}

kalloc中,当发现freelist已经用完后,需要向其他CPU的freelist借用节点

void *
kalloc(void)
{
  struct run *r;

  push_off();
  int ncpu = cpuid();

  acquire(&kmem[ncpu].lock);
  r = kmem[ncpu].freelist;
  if(r) {
    kmem[ncpu].freelist = r->next;
  } 
  release(&kmem[ncpu].lock);
  if (!r) {
    // steal other cpu's freelist
    for (int i = 0; i < NCPU; i++) {
      if (i == ncpu) continue;
      acquire(&kmem[i].lock);
      r = kmem[i].freelist;     
      if (r) {
        kmem[i].freelist = r->next;
        release(&kmem[i].lock);
        break;
      }
      release(&kmem[i].lock);
    }
  }
  pop_off();
  
  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}

Buffer cache

实验要求:xv6文件系统的buffer cache采用了一个全局的锁bcache.lock来负责对buffer cache进行读写保护,当xv6执行读写文件强度较大的任务时会产生较大的锁竞争压力,因此需要一个哈希表,将buf entry以buf.blockno为键哈希映射到这个哈希表的不同的BUCKET中,给每个BUCKET一个锁,NBUCKET最好选择素数,这里选择13。注意:这个实验不能像上一个一样给每个CPU一个bcache,因为文件系统在多个CPU之间是真正实现共享的,否则将会造成一个CPU只能访问某些文件的问题。

这里实验让我们可以使用ticks作为时间戳,来代替原来的双向链表实现LRU的功能。

kernel/bio.c中,首先设置NBUCKET宏定义为13,声明外部变量ticks,修改bcache以为每个BUCKET设置一个链表头,并设置一个锁

#define NBUCKET 13

uint extern ticks;

struct {
  struct spinlock lock[NBUCKET];
  struct buf buf[NBUF];

  // Linked list of all buffers, through prev/next.
  // Sorted by how recently the buffer was used.
  // head.next is most recent, head.prev is least.
  struct buf head[NBUCKET];
} bcache;

修改kernel/buf.h中的buf结构体,删除struct buf *prev,即将双向链表变为单向链表,添加uint time这个成员变量作为时间戳。

实现一个简单的哈希函数

int hash (int n) {
  int result = n % NBUCKET;
  return result;
}

修改binit函数,为每个bcache.lock以及b->lock进行初始化,并将所有buf先添加到bucket 0哈希表中

void
binit(void)
{
  struct buf *b;

  for (int i = 0; i < NBUCKET; i++) {
    initlock(&bcache.lock[i], "bcache");
  }

  bcache.head[0].next = &bcache.buf[0];
  // for initialization, append all bufs to bucket 0
  for (b = bcache.buf; b < bcache.buf+NBUF-1; b++) {
    b->next = b+1;
    initsleeplock(&b->lock, "buffer");
  }
}

修改bget,先查找当前哈希表中有没有和传入参数devblockno相同的buf。先要将blockno哈希到一个id值,然后获得对应id值的bcache.lock[id]锁,然后在这个bucket id哈希链表中查找符合对应条件的buf,如果找到则返回,返回前释放掉bcache.lock[id],并对buf加sleeplock。

int id = hash(blockno);
acquire(&bcache.lock[id]);
b = bcache.head[id].next;
while (b) {
    if (b->dev == dev && b->blockno == blockno) {
        b->refcnt++;
        if (holding(&bcache.lock[id]))
            release(&bcache.lock[id]);
        acquiresleep(&b->lock);
        return b;
    }
    b = b->next;
}

如果没有找到对应的buf,需要在整个哈希表中查找LRU(least recently used)buf,将其替换掉。这里由于总共有NBUCKET个哈希表,而此时一定是持有bcache.lock[id]这个哈希表的锁的,因此当查找其他哈希表时,需要获取其他哈希表的锁,这时就会有产生死锁的风险。风险1:查找的哈希表正是自己本身这个哈希表,在已经持有自己哈希表锁的情况下,不能再尝试acquire一遍自己的锁。风险2:假设有2个进程同时要进行此步骤,进程1已经持有了哈希表A的锁,尝试获取哈希表B的锁,进程2已经持有了哈希表B的锁,尝试获取哈希表A的锁,同样会造成死锁,因此要规定一个规则,是的当持有哈希表A的情况下如果能够获取哈希表B的锁,则当持有哈希表B锁的情况下不能够持有哈希表A的锁。该规则在can_lock函数中实现。

int can_lock(int id, int j) {
  int num = NBUCKET/2;
  if (id <= num) {
    if (j > id && j <= (id+num))
      return 0;
  } else {
    if ((id < j && j < NBUCKET) || (j <= (id+num)%NBUCKET)) {
      return 0;
    }
  }
  return 1;
}

其中id是已经持有的锁,j是判断是否能获取该索引哈希表的锁。这个规则实际上规定了在持有某一个锁的情况下,只能再尝试获取NBCUKET/2个哈希表锁,另一半哈希表锁是不能获取的。

确定了这个规则之后,尝试遍历所有的哈希表,通过b->time查找LRUbuf。先判断当前的哈希表索引是否为id,如果是,则不获取这个锁(已经获取过它了),但是还是要遍历这个哈希表的;同时也要判断当前哈希表索引是否满足can_lock规则,如果不满足,则不遍历这个哈希表,直接continue。如果哈希表索引j既不是id,也满足can_lock,则获取这个锁,并进行遍历。当找到了一个当前情况下的b->time最小值时,如果这个最小值和上一个最小值不在同一个哈希表中,则释放上一个哈希表锁,一直持有拥有当前情况下LRUbuf这个哈希表的锁,直到找到新的LRUbuf且不是同一个哈希表为止。找到LRUbuf后,由于此时还拥有这个哈希表的锁,因此可以直接将这个buf从该哈希链表中剔除,并将其append到bucketid哈希表中,修改这个锁的devblocknovalidrefcnt等属性。最后释放所有的锁。

int index = -1;
uint smallest_tick = __UINT32_MAX__;
// find the LRU unused buffer
for (int j = 0; j < NBUCKET; ++j) {
    if (j!=id && can_lock(id, j)) {
        // if j == id, then lock is already acquired
        // can_lock is to maintain an invariant of lock acquisition order
        // to avoid dead lock
        acquire(&bcache.lock[j]);
    } else if (!can_lock(id, j)) {
        continue;
    }
    b = bcache.head[j].next;
    while (b) {
        if (b->refcnt == 0) {
            if (b->time < smallest_tick) {
                smallest_tick = b->time;
                if (index != -1 && index != j && holding(&bcache.lock[index])) release(&bcache.lock[index]);
                index = j;
            }   
        }
        b = b->next;
    }
    if (j!=id && j!=index && holding(&bcache.lock[j])) release(&bcache.lock[j]);
}
if (index == -1) panic("bget: no buffers");
b = &bcache.head[index];

while (b) {
    if ((b->next)->refcnt == 0 && (b->next)->time == smallest_tick) {
        selected = b->next;
        b->next = b->next->next;
        break;
    }
    b = b->next;
}
if (index != id && holding(&bcache.lock[index])) release(&bcache.lock[index]);
b = &bcache.head[id];
while (b->next) {
    b = b->next;
}
b->next = selected;
selected->next = 0;
selected->dev = dev;
selected->blockno = blockno;
selected->valid = 0;
selected->refcnt = 1;
if (holding(&bcache.lock[id]))
    release(&bcache.lock[id]);
acquiresleep(&selected->lock);
return selected;
}

修改brelse。当b->refcnt==0时,说明这个buf已经被使用完了,可以进行释放,为其加上时间戳

void
brelse(struct buf *b)
{
  if(!holdingsleep(&b->lock))
    panic("brelse");

  releasesleep(&b->lock);

  int id = hash(b->blockno);
  acquire(&bcache.lock[id]);
  b->refcnt--;
  if (b->refcnt == 0) {
    b->time = ticks;
  }
  
  release(&bcache.lock[id]);
}

修改bpinbunpin,将bcache.lock修改为bcache.lock[id]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值