Memory allocator
kalloctest中锁争用的根本原因是kalloc()有一个空闲列表,由一个锁保护。要消除锁争用,您必须重新设计内存分配器,以避免使用单个锁和列表。基本思想是为每个CPU维护一个空闲列表,每个列表都有自己的锁。
这个实验让我们为每个CPU维护一个空闲的内存列表,从而避免使用一个空闲的内存列表时发生争用。
//proc.h
struct cpu {
struct proc *proc; // The process running on this cpu, or null.
struct context context; // swtch() here to enter scheduler().
int noff; // Depth of push_off() nesting.
int intena; // Were interrupts enabled before push_off()?
struct {
struct spinlock lock;
struct run *freelist;
} kmem;//为CPU结构新增一个空闲内存列表元素。
};
//kalloc.c
void
kinit()
{
for(int i = 0; i < NCPU;i++)//初始化内存时,初始化每个CPU的内存锁
initlock(&cpus[i].kmem.lock, "kmem");
freerange(end, (void*)PHYSTOP);
}
void
kfree(void *pa)
{
struct run *r;
push_off();
int c = cpuid(); //获取当前CPU的ID号
pop_off();
//int i;
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;
acquire(&cpus[c].kmem.lock); //将pa重新加入CPU的空闲内存。
r->next = cpus[c].kmem.freelist;
cpus[c].kmem.freelist = r;
release(&cpus[c].kmem.lock);
}
void *
kalloc(void)
{
struct run *r;
int i;
push_off();
int c = cpuid(); //获取当前CPU的ID号
pop_off();
if(!cpus[c].kmem.freelist) { //当前CPU的空闲内存为空时,从其他CPU的空闲内存中窃取内存给当前CPU使用
for(i = 0; i < NCPU; i++) {
acquire(&cpus[i].kmem.lock);
if(cpus[i].kmem.freelist) {
r = cpus[i].kmem.freelist;
if(r) {
cpus[i].kmem.freelist = r->next;
acquire(&cpus[c].kmem.lock);
r->next = cpus[c].kmem.freelist;
cpus[c].kmem.freelist = r;
release(&cpus[c].kmem.lock);
release(&cpus[i].kmem.lock);
break;
}
}
release(&cpus[i].kmem.lock);
}
}
acquire(&cpus[c].kmem.lock);
r = cpus[c].kmem.freelist;
if(r)
cpus[c].kmem.freelist = r->next;
release(&cpus[c].kmem.lock);
if(r)
memset((char*)r, 5, PGSIZE); // fill with junk
return (void*)r;
}
Buffer cache
修改块缓存,以便在运行bcachetest时,bcache(buffer cache的缩写)中所有锁的acquire循环迭代次数接近于零。
当不同进程争用文件系统时,会造成锁被频繁调用,我们的目的就是改善这个情况。
//bio.c
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.
struct spinlock bucket[13]; //修改锁的数量,降低冲突。
} bcache;
void
binit(void)
{
struct buf *b;
int i;
initlock(&bcache.lock, "bcache");
for(i = 0; i < 13; i++) { //初始化所有锁
initlock(&bcache.bucket[i], "bucket");
}
// Create linked list of buffers
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
initsleeplock(&b->lock, "buffer");
}
}
static struct buf*
bget(uint dev, uint blockno)
{
struct buf *b;
struct buf *tmp = 0;
uint minTime = 0;
//acquire(&bcache.lock);
acquire(&bcache.bucket[blockno % 13]); //由当前的blockno锁住对应的锁
// Is the block already cached?
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
if(b->dev == dev && b->blockno == blockno){
b->refcnt++;
release(&bcache.bucket[blockno % 13]);//解锁
//release(&bcache.lock);
acquiresleep(&b->lock);//唤醒睡眠锁
return b;
}
}
release(&bcache.bucket[blockno % 13]);//解锁blockno对应的锁
// Not cached.//当没有block可用时,我们查找最近最少使用的,将其替换
// Recycle the least recently used (LRU) unused buffer.
acquire(&bcache.bucket[blockno % 13]);//先锁住blockno对应的锁
acquire(&bcache.lock); //锁住大锁
//先重新查找一次,避免解锁的时候有block可用
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
if(b->dev == dev && b->blockno == blockno){
b->refcnt++;
release(&bcache.lock);
release(&bcache.bucket[blockno % 13]);
//release(&bcache.lock);
acquiresleep(&b->lock);
return b;
}
}
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
if(b->refcnt == 0 && (minTime > b->time || tmp == 0)) { //查找时间最长没有用过的
minTime = b->time;
tmp = b;
}
if(tmp) {//调用这个buf
tmp->dev = dev;
tmp->blockno = blockno;
tmp->valid = 0;
tmp->refcnt = 1;
tmp->time = ticks;
release(&bcache.lock);//按顺序解锁
release(&bcache.bucket[blockno % 13]);
acquiresleep(&tmp->lock);//唤醒
return tmp;
}
}
panic("bget: no buffers");
}
void
brelse(struct buf *b)
{
uint blockno;
if(!holdingsleep(&b->lock))
panic("brelse");
releasesleep(&b->lock);
//acquire(&bcache.lock);
blockno = b->blockno;
acquire(&bcache.bucket[blockno % 13]);
b->refcnt--;
if (b->refcnt == 0) { //只有当引用计数为0,更新time为最后调用的时间。
/*acquire(&bcache.lock);
// no one is waiting for it.
b->next->prev = b->prev;
b->prev->next = b->next;
b->next = bcache.head.next;
b->prev = &bcache.head;
bcache.head.next->prev = b;
bcache.head.next = b;
release(&bcache.lock);*/
b->time = ticks;
}
release(&bcache.bucket[blockno % 13]);
//release(&bcache.lock);
}
void
bpin(struct buf *b) {
//acquire(&bcache.lock);
acquire(&bcache.bucket[b->blockno % 13]); //都改为锁住对应blockno的锁
b->refcnt++;
release(&bcache.bucket[b->blockno % 13]);
//release(&bcache.lock);
}
void
bunpin(struct buf *b) {
//acquire(&bcache.lock);
acquire(&bcache.bucket[b->blockno % 13]);
b->refcnt--;
release(&bcache.bucket[b->blockno % 13]);
//release(&bcache.lock);
}
//buf.h
uint time;//为buf结构增加时间戳。
这个代码可能还是有点问题,反正有时候会抽风不能通过,但是make clean之后再次运行又可以通过,应该是有什么奇奇怪怪的问题,要考试了不管了,以后再看(遥远的以后。
这个实验主要就是锁的使用,让我们重新设计锁,减少锁的冲突,每个对应的部分应该有对应的锁,而不是用一把大锁,锁住所有临界区,虽然这样不会出问题,但会导致大量的冲突,浪费大量时间,与我们希望加速程序运行的目的相违背。