实验思想
实验文档说:想要利用hash bucket思想修改缓冲区缓存,来减少对bcache.lock的争用。
在xv6系统中,bcache用于缓存磁盘块,以减少磁盘I/O开销,然而,原始的xv6实现中使用了一个全局锁bcache.lock来保护整个缓冲区缓存系统,这在并发场景中会导致锁竞争严重,限制了多核处理器环境中的并发性能:当前的bcache结构统一管理所有磁盘块缓存,所有对buffer cache的访问都需要加解锁,而当多个CPU核心同时运行多个进程,每个进程都在频繁地访问磁盘块,每个访问都要走缓存块,并发操作buffer cache就会频繁发生这种锁竞争,大多数线程会因为等待bcache.lock而阻塞或自旋浪费CPU时间。
-
hash bucket的思想
将buffer cache分成多个桶,每个桶有独立的锁,通过哈希函数将不同的block映射到不同的桶中,这样访问块时只需锁对应的桶,锁竞争的概率大幅降低,系统整体可以并发处理多个缓冲块操作,从而实现更细粒度的锁控制。
结构体与变量设置
此处一定要!!!区分BUCKETSIZE和BUFFERSIZE!!!
刚开始实验时因为这俩没区分开,初始化的时候都设为了BUFFERSIZE,一直卡死o(╥﹏╥)o
// bio.c
#define BUCKETSIZE 13 // number of hashing buckets
#define BUFFERSIZE 5 // number of available buskets per busket
extern uint ticks;
int hash(uint blockno)
{
return blockno % BUCKETSIZE;
}
struct {
struct spinlock lock;
struct buf buf[BUFFERSIZE];
}bcachebucket[BUCKETSIZE];
// buf.h
uint lastuse;
buf结构体
// buf.h
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 lastuse; //last use time
};
-
valid:表示缓冲区的数据是否有效,是否读取与磁盘
-
dev:设备号
-
blockno:块号
-
lock:睡眠锁
-
prev、next:双向链表中指向前后节点
-
refcnt:记录使用该缓冲块的进程数
初始化
binit函数:使用多个哈希桶bcachebucket进行初始化,每个桶有一个单独的锁bcachebucket[i].lock。每个桶中包含一组缓冲区bcachebucket[i].buf,每个缓冲区都有各自的睡眠锁。
void
binit(void)
{
for(int i = 0; i < BUCKETSIZE; i++)
{
initlock(&bcachebucket[i].lock, "bcachebucket");
for(int j = 0; j < BUFFERSIZE; j++)
{
initsleeplock(&bcachebucket[i].buf[j].lock, "buffer");
bcachebucket[i].buf[j].lastuse = 0;
bcachebucket[i].buf[j].refcnt = 0;
}
}
}
bpin、bunpin函数
增加、减少缓冲区的引用计数。
修改:将原本的bcache修改为bcachebucket中的bucketno项,其中bucketno为哈希映射后得到的桶号。
void
bpin(struct buf *b) {
int blockno= hash(b->blockno);
acquire(&bcachebucket[blockno].lock);
b->refcnt++;
release(&bcachebucket[blockno].lock);
}
void
bunpin(struct buf *b) {
int blobkno = hash(b->blockno);
acquire(&bcachebucket[blobkno].lock);
b->refcnt--;
release(&bcachebucket[blobkno].lock);
}
修改bget函数
根据blockno哈希计算出对应的bucketno后,获取锁,进行查找:遍历这个bucket的缓冲区,如果没能找到dev、blockno都对应的块,则尝试在当前bucket里试图寻找一个空闲的来分配。
首先计算哈希值,获取对应桶的锁。然后遍历该桶的所有缓冲区,检查该块是否已经缓存,如果找到了对应的设备号和块号,则增加refcnt数,更新最近使用时间;如果没有被缓存过,则再次遍历缓冲区,查找没有被使用过且最近最少使用的缓冲区,若成功找到,则更新其相应信息。最后释放桶的锁,获取缓冲区的睡眠锁。若桶内没有找到,则需要在其他的桶内寻找,遍历所有其他的桶的所有缓冲区,一样的判断条件寻找缓存块,这里为了防止过多的循环,我们设置只要在一个桶内找到了符合要求的块就直接赋值,不再遍历所有的。
static struct buf*
bget(uint dev, uint blockno)
{
struct buf *b;
int bucketno = hash(blockno);
acquire(&bcachebucket[bucketno].lock);
// Is the block already cached?
for(int i = 0; i < BUFFERSIZE; i++)
{
b = &bcachebucket[bucketno].buf[i];
if(b->dev == dev && b->blockno == blockno)
{
b->refcnt++;
b->lastuse = ticks;
release(&bcachebucket[bucketno].lock);
acquiresleep(&b->lock);
return b;
}
}
// Not cached.
// Recycle the least recently used (LRU) unused buffer.
int index = -1;
uint min_ticks = 0xffffffff;
for(int i = 0; i < BUFFERSIZE; i++)
{
b = &bcachebucket[bucketno].buf[i];
if(b->refcnt == 0 && b -> lastuse < min_ticks)
{
index = i;
min_ticks = b -> lastuse;
}
}
if (index != -1) {
b = &bcachebucket[bucketno].buf[index];
b->dev = dev;
b->blockno = blockno;
b->valid = 0;
b->refcnt = 1;
b->lastuse = ticks;
release(&bcachebucket[bucketno].lock);
acquiresleep(&b->lock);
return b;
}
else
{
// other bucket
for (int i = 0; i < BUCKETSIZE; i++) {
if (i == bucketno)
continue;
acquire(&bcachebucket[i].lock);
for (int j = 0; j < BUFFERSIZE; j++) {
b = &bcachebucket[i].buf[j];
if (b->refcnt == 0 && b -> lastuse < min_ticks)
{
index = j;
min_ticks = b -> lastuse;
break;
}
}
if(index != -1)
{
b = &bcachebucket[i].buf[index];
b -> dev = dev;
b -> blockno = blockno;
b -> lastuse = ticks;
b -> valid = 0;
b -> refcnt = 1;
release(&bcachebucket[i].lock);
acquiresleep(&b->lock);
return b;
}
release(&bcachebucket[i].lock);
}
}
release(&bcachebucket[bucketno].lock);
panic("bget: no buffers");
}
brelse函数
首先判断是否获取到该块的睡眠锁,若无法获取,即锁被占用,证明这一块还在使用中,不能被释放,报错。正确获取,则证明已经未使用,可以进行设置,首先释放睡眠锁,这块可以再重新被启用。然后根据块号获取哈希桶号,获取桶自旋锁。再将使用进程数减一,没有使用的进程时,更新时间片,然后释放桶的自旋锁。
void
brelse(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("brelse");
releasesleep(&b->lock);
int bucketno = hash(b->blockno);
acquire(&bcachebucket[bucketno].lock);
b->refcnt--;
if (b->refcnt == 0) {
b->lastuse = ticks;
}
release(&bcachebucket[bucketno].lock);
}