6.S081-2021 locks
buffer cache
原来的实现是一个双向链表,每次进行文件的读写操作都要锁住整个表,显然大大降低了系统的并发度。
所以可以仿照Java中currentHashMap的思路,使用分段锁,每个hash桶上一个锁。
但是在进行缓存驱逐的时候需要注意死锁的情况。
比如我们哈希值为2的缓存块不存在并且哈希值为5的缓冲块不存在。这时候CPU1会先获取桶2的锁然后遍历所有的桶,遍历到5的时候,因为桶5的锁被另外一个CPU2持有。而CPU2获取桶5的锁之后,同样需要遍历所有的桶查找,这时候桶2又被CPU1持有。形成循环等待,从而造成死锁。
#include "types.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "riscv.h"
#include "defs.h"
#include "fs.h"
#include "buf.h"
#define NBUCKET 13
int hash(int no){
return no%NBUCKET;
}
struct hashbuf{
struct spinlock lock;
struct buf head;
};
struct {
struct spinlock lock;
struct buf buf[NBUF];
struct hashbuf bucket[NBUCKET];
// 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;
} bcache;
void
binit(void)
{
struct buf *b;
initlock(&bcache.lock, "bcache");
// 初始化散列桶的锁和头节点
for(int i = 0; i<NBUCKET; i++){
initlock(&bcache.bucket[i].lock,"bache");
bcache.bucket[i].head.prev = &bcache.bucket[i].head;
bcache.bucket[i].head.next = &bcache.bucket[i].head;
}
// 将所有的buf都先添加到0号桶
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
b->next = bcache.bucket[0].head.next;
b->prev = &bcache.bucket[0].head;
initsleeplock(&b->lock, "buffer");
bcache.bucket[0].head.next->prev = b;
bcache.bucket[0].head.next = b;
}
}
// 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;
// acquire(&bcache.lock);
int index = hash(blockno);
acquire(&bcache.bucket[index].lock);
// Is the block already cached?
for(b = bcache.bucket[index].head.next; b != &bcache.bucket[index].head; b = b->next){
if(b->dev == dev && b->blockno == blockno){
b->refcnt++;
b->timestamp = ticks;
release(&bcache.bucket[index].lock);
// release(&bcache.lock);
acquiresleep(&b->lock);
return b;
}
}
release(&bcache.bucket[index].lock);
uint oldestTime = __UINT32_MAX__;
struct buf *item;
int bucketId = -1;
for(int i=index,loop=0;loop<NBUCKET;loop++){
i=(index+loop)%NBUCKET;
acquire(&bcache.bucket[i].lock);
for(item = bcache.bucket[i].head.next; item != &bcache.bucket[i].head; item = item->next){
if(item->refcnt == 0 && item->timestamp < oldestTime){
oldestTime = item->timestamp;
b = item;
bucketId = i;
}
}
release(&bcache.bucket[i].lock);
}
if(oldestTime!=__UINT32_MAX__){
// 驱逐
acquire(&bcache.bucket[bucketId].lock);
b->next->prev = b->prev;
b->prev->next = b->next;
release(&bcache.bucket[bucketId].lock);
acquire(&bcache.bucket[index].lock);
b->next = bcache.bucket[index].head.next;
b->prev = &bcache.bucket[index].head;
bcache.bucket[index].head.next->prev = b;
bcache.bucket[index].head.next = b;
b->dev = dev;
b->blockno = blockno;
b->valid = 0;
b->refcnt = 1;
b->timestamp = ticks;
release(&bcache.bucket[index].lock);
acquiresleep(&b->lock);
return b;
}
panic("bget: no buffers");
}
// Return a locked buf with the contents of the indicated block.
struct buf*
bread(uint dev, uint blockno)
{
struct buf *b;
b = bget(dev, blockno);
if(!b->valid) {
virtio_disk_rw(b, 0);
b->valid = 1;
}
return b;
}
// Write b's contents to disk. Must be locked.
void
bwrite(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("bwrite");
virtio_disk_rw(b, 1);
}
// 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 index = hash(b->blockno);
acquire(&bcache.bucket[index].lock);
b->refcnt--;
b->timestamp = ticks;
release(&bcache.bucket[index].lock);
}
void
bpin(struct buf *b) {
int index = hash(b->blockno);
acquire(&bcache.bucket[index].lock);
b->refcnt++;
release(&bcache.bucket[index].lock);
}
void
bunpin(struct buf *b) {
int index = hash(b->blockno);
acquire(&bcache.bucket[index].lock);
b->refcnt--;
release(&bcache.bucket[index].lock);
}