Memory allocator
这个exericise的工作是通过为每个CPU维护一个freelist来减少kalloc时的锁竞争,当CPU对应的freelist为空时,从其他CPU的freelist中窃取page。
1.为每个CPU都维护一个freelist
struct {
struct spinlock lock;
struct run *freelist;
} kmem[NCPU];
2.kinit时为每个CPU对应freelist初始化锁
void
kinit()
{
for (int i = 0; i < NCPU; i++)
initlock(&kmem[i].lock, "kmem");
freerange(end, (void*)PHYSTOP);
}
3.freerange时将所有的page都放到同一个CPU对应的freelist中,freerange函数保持不变
4.kalloc时获取当前cpu的cpuid,然后根据cpuid去对应的freelist中查找是否有可用的page,如果没有到其他cpu的freelist中查找
void *
kalloc(void)
{
struct run *r;
push_off();
int cpu_id = cpuid();
pop_off();
acquire(&kmem[cpu_id].lock);
r = kmem[cpu_id].freelist;
if(r)
kmem[cpu_id].freelist = r->next;
else {
for (int i = 0; i < NCPU; i++) {
if (i != cpu_id) {
acquire(&kmem[i].lock);
r = kmem[i].freelist;
if(r)
kmem[i].freelist = r->next;
release(&kmem[i].lock);
if (r)
break;
}
}
}
release(&kmem[cpu_id].lock);
if(r)
memset((char*)r, 5, PGSIZE); // fill with junk
return (void*)r;
}
4.kfree时将page返还到当前cpu的freelist中
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 cpu_id = cpuid();
pop_off();
acquire(&kmem[cpu_id].lock);
r->next = kmem[cpu_id].freelist;
kmem[cpu_id].freelist = r;
release(&kmem[cpu_id].lock);
}
buffer cache
buffer cache的作用
1.确保一个disk block在内存只有一个副本,并且每次只被一个kernel thread使用
2.对被频繁使用的disk block进行缓存,从而减少对磁盘进行读操作的开销
实现
这个exercise的目的是减少bcache中的锁争用问题,xv6中的实现是将bcache中的buf组成一个链表,bget时从前往后查找看对应的dev和block是否存在对应的buffer,如果不存在对应的buffer则从后往前查找,返回首个引用计数为0的buffer。brelse中将释放的buffer插入到链表的头部,从而使得链表整体符合频繁使用的buffer在前的顺序。
为了减少锁争用,使用分BUCKET的方式,在实现中使用了7个BUCKET,通过对blockno进行哈希来获取到block对应的BUCKET。不使用原来的链表方案,而是使用ticks来记录buffer上一次的使用时间,如果block没有对应的cache,则将buf数组中最早使用的buffer返回给调用者。
1.在buf结构体中添加nticks成员记录buffer的使用时间
struct buf {
int valid; // has data been read from disk?
int disk; // does disk "own" buf?
uint dev;
uint blockno;
struct sleeplock lock;
uint refcnt;
struct buf *prev; // LRU cache list
struct buf *next;
uchar data[BSIZE];
uint nticks;
};
2.使用多个BUCKET,从而减少使用bcache时锁的竞争
#define NBUCKET 7
struct {
struct spinlock lock;
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.
} bcache[NBUCKET];
3.binit中初始化bcache中的锁以及每个buffer的锁,并对buffer进行初始化
void
binit(void)
{
struct buf *b;
for (int i = 0; i < NBUCKET; i++) {
initlock(&bcache[i].lock, "bcache");
for(b = bcache[i].buf; b < bcache[i].buf+NBUF; b++){
initsleeplock(&b->lock, "buffer");
b->refcnt = 0;
b->nticks = 0;
}
}
}
4.bget时通过对blockno进行哈希获取对应的bucket,查找bucket中的是否存在对应的buffer,如果存在对应的buffer直接返回该buffer,如果不存在对应的buffer则在bucket中查找nticks最小且引用计数为0的buffer。
// Look through buffer cache for block on device dev.
// If not found, allocate a buffer.
// In either case, return locked buffer.
static struct buf*
bget(uint dev, uint blockno)
{
struct buf *b;
int bucket_idx = blockno % NBUCKET;
acquire(&bcache[bucket_idx].lock);
int min_idx = -1;
uint min_ticks = __UINT32_MAX__;
// Is the block already cached?
for(int i = 0; i < NBUF; i++){
// 如果存在对应buffer,释放bcache的锁,增加buffer的引用以及获取buffer的锁
b = bcache[bucket_idx].buf + i;
if(b->dev == dev && b->blockno == blockno){
b->refcnt++;
release(&bcache[bucket_idx].lock);
acquiresleep(&b->lock);
return b;
}
else {
// printf("refcnt: %d, ntick: %d\n", b->refcnt, b->nticks);
if (b->refcnt == 0 && b->nticks < min_ticks) {
min_idx = i;
min_ticks = b->nticks;
}
}
}
// Not cached.
// Recycle the least recently used (LRU) unused buffer.
// 如果不存在对应的cache且找到可用buffer
if (min_idx != -1) {
b = bcache[bucket_idx].buf + min_idx;
b->dev = dev;
b->blockno = blockno;
b->valid = 0;
b->refcnt = 1;
release(&bcache[bucket_idx].lock);
acquiresleep(&b->lock);
return b;
}
panic("bget: no buffers");
}
5.brelse中释放的操作修改为更新buffer的nticks
// Release a locked buffer.
// Move to the head of the most-recently-used list.
void
brelse(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("brelse");
releasesleep(&b->lock);
int bucket_idx = b->blockno % NBUCKET;
acquire(&bcache[bucket_idx].lock);
b->refcnt--;
// 如果引用为0,释放掉该buffer
if (b->refcnt == 0) {
// no one is waiting for it.
// 将该buffer移到头节点
b->nticks = ticks;
}
release(&bcache[bucket_idx].lock);
}
6.bpin和bunpin修改为通过对blockno哈希获取bucket对应的锁
void
bpin(struct buf *b) {
int bucket_idx = b->blockno % NBUCKET;
acquire(&bcache[bucket_idx].lock);
b->refcnt++;
release(&bcache[bucket_idx].lock);
}
void
bunpin(struct buf *b) {
int bucket_idx = b->blockno % NBUCKET;
acquire(&bcache[bucket_idx].lock);
b->refcnt--;
release(&bcache[bucket_idx].lock);
}