首页 > 其他分享 >【XV6】 locks

【XV6】 locks

时间:2024-02-14 19:11:21浏览次数:26  
标签:struct lock kmem XV6 locks bcache buf CPU

代码:https://github.com/JasenChao/xv6-labs.git

内存分配器

单个空闲内存列表可能引起多个CPU的频繁锁争用,题目要求设计内存分配器,让每个CPU维护一个空闲内存列表,不同CPU的分配和释放可以并行执行,但如果一个CPU可用列表为空,而其他CPU可用列表不为空,则这个CPU必须窃取其他CPU的空闲内存,从而引入了锁争用。

kernel/kalloc.c中实现每个CPU的空闲列表,以及相应的内存分配和释放函数。

首先把kmem改为每个CPU维护一个:

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

kinit()时将每个锁都初始化:

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

kalloc()函数修改为单个CPU并行,空闲列表不足时获取其他CPU的内存锁以偷窃内存:

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

  push_off();                           // 关中断
  int id = cpuid();
  acquire(&kmem[id].lock);              // 获取当前CPU的内存锁
  r = kmem[id].freelist;
  if(r){                                // 如果当前CPU有空闲内存
    kmem[id].freelist = r->next;        // 让出r,释放锁
  }
  else{                                 // 当前CPU没有空闲内存了
    for(int i = 0; i < NCPU; ++i){      // 从其他CPU找
      if(i != id){
        acquire(&kmem[i].lock);         // 获取其他CPU的锁
        if(kmem[i].freelist){           // 如果这个CPU有空闲内存,那就抢走
          r = kmem[i].freelist;
          kmem[i].freelist = r->next;
          release(&kmem[i].lock);
          break;
        }
        release(&kmem[i].lock);
      }
    }
  }
  release(&kmem[id].lock);
  pop_off();                            // 开中断

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}

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 id = cpuid();
  acquire(&kmem[id].lock);
  r->next = kmem[id].freelist;
  kmem[id].freelist = r;
  release(&kmem[id].lock);
  pop_off();
}

Cache缓冲区

类似于上一个问题,多个进程在读写磁盘时都会用到Cache,重复读取不同的文件会引发bcache.lock争用,题目要求降低获取bcache锁的迭代次数(小于500)。

buf.h中根据提示,用质数(如13)个存储桶来减少哈希冲突的可能性,同时在buf中加入时间戳,选取空白缓存时选择refcnt==0且时间最小的:

#define NBUCKET 13

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 time;
};

bio.c中根据提示,删除缓冲区列表,将bcache改为哈希存储桶的个数:

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];

binit函数中,将所有bcache和其包含的buf中的锁都初始化:

void
binit(void)
{
  struct buf *b;

  for(int i = 0; i < NBUCKET; ++i){
    for(b = bcache[i].buf; b < bcache[i].buf + NBUF; b++){
      initsleeplock(&b->lock, "buffer");
    }
    initlock(&bcache[i].lock, "bcache");
  }
}

此时可能会出现一个问题就是锁的数量不够用了,这里会导致系统里面需要初始化的锁太多了,将spinlock.c中锁的数量改大,比如改到800:

#define NLOCK 800

bget函数的逻辑需要修改,先取到对应的哈希存储桶,遍历其中的buf,如果找到了dev和blockno一致的缓存,引用+1后直接返回,如果没有就取refcnt==0并且时间最早的缓冲区:

static struct buf*
bget(uint dev, uint blockno)
{
  struct buf *b = 0;
  struct buf *min_b = 0;
  int i = blockno % NBUCKET;
  uint min_time = -1;

  acquire(&bcache[i].lock);

  // Is the block already cached?
  for(b = bcache[i].buf; b < bcache[i].buf + NBUF; b++){
    if(b->dev == dev && b->blockno == blockno){
      b->refcnt++;
      release(&bcache[i].lock);
      acquiresleep(&b->lock);
      return b;
    }
    if(b->refcnt == 0 && b->time < min_time)
    {
      min_time = b->time;
      min_b = b;
    }
  }

  b = min_b;
  if(b!=0)
  {
    b->dev = dev;
    b->blockno = blockno;
    b->valid = 0;
    b->refcnt = 1;
    release(&bcache[i].lock);
    acquiresleep(&b->lock);
    return b;
  }
  panic("bget: no buffers");
}

brelse函数中,只要获取对应的哈希存储桶,并且引用数-1即可释放,当引用数为0时将最新一次被使用的时间戳更新上去,方便下次bget

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

  releasesleep(&b->lock);
  int i = b->blockno % NBUCKET;

  acquire(&bcache[i].lock);
  b->refcnt--;
  if (b->refcnt == 0) {
    // no one is waiting for it.
    b->time = ticks;
  }
  
  release(&bcache[i].lock);
}

bpinbunpin函数只需要将原来获取bcache的代码改为获取对应的哈希存储桶:

void
bpin(struct buf *b) {
  int i = b->blockno % NBUCKET;
  acquire(&bcache[i].lock);
  b->refcnt++;
  release(&bcache[i].lock);
}

void
bunpin(struct buf *b) {
  int i = b->blockno % NBUCKET;
  acquire(&bcache[i].lock);
  b->refcnt--;
  release(&bcache[i].lock);
}

测试结果

使用make grade测试,结果如下:

== Test running kalloctest ==
$ make qemu-gdb
(51.8s)
== Test   kalloctest: test1 ==
  kalloctest: test1: OK
== Test   kalloctest: test2 ==
  kalloctest: test2: OK
== Test   kalloctest: test3 ==
  kalloctest: test3: OK
== Test kalloctest: sbrkmuch ==
$ make qemu-gdb
kalloctest: sbrkmuch: OK (5.4s)
== Test running bcachetest ==
$ make qemu-gdb
(7.2s)
== Test   bcachetest: test0 ==
  bcachetest: test0: OK
== Test   bcachetest: test1 ==
  bcachetest: test1: OK
== Test usertests ==
$ make qemu-gdb
usertests: OK (41.3s)

标签:struct,lock,kmem,XV6,locks,bcache,buf,CPU
From: https://www.cnblogs.com/JasenChao/p/18015454

相关文章

  • 【XV6】 networking
    代码:https://github.com/JasenChao/xv6-labs.gitE1000网络设备驱动题目已经在kernel/e1000.c中给出了E1000的初始化函数和发送接收函数,要求完善发送和接收的功能。其他相关的代码,上层的网络协议在kernel/net.c和kernel/net.h中。PCI总线上搜索网卡的代码在kernel/pci.c中://t......
  • [MIT 6.S081] Lab: Copy-on-Write Fork for xv6
    Lab:Copy-on-WriteForkforxv6在这个实验中,我们要为xv6实现cowfork。Implementcopy-onwrite根据写时复制的方式,我们在复制页面的时候应该采用的是将父级的物理页面映射到子级的页面,因此我们需要修改kernel/vm.c中的uvmcopy,将页面复制修改为映射的方式,同时应当将......
  • 【JDK】LockSupport 工具类
    1 前言LockSupport工具类最近复习到这个类了,之前也没做笔记,这里简单回顾下哈。JDK中的rt.jar包里面的LockSupport是个工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用L......
  • MIT 6.1810 Lab: Copy-on-Write Fork for xv6
    lab网址:https://pdos.csail.mit.edu/6.828/2022/labs/cow.htmlxv6Book:https://pdos.csail.mit.edu/6.828/2022/xv6/book-riscv-rev3.pdfImplementcopy-on-writefork这部分需要我们实现写时拷贝,题目给出解决方案为,当fork时,将父子进程的页表项都设置为只度,当发生写错误时,在处......
  • ReentrantLock源码分析、LockSuppor、ReentrantReadWriteLock、锁优化的方法
    ReentrantLock类图我们看一下重入锁ReentrantLock类关系图,它是实现了Lock接口的类。NonfairSync和FairSync都继承自抽象类Sync,在ReentrantLock中有非公平锁NonfairSync和公平锁FairSync的实现。在重入锁ReentrantLock类关系图中,我们可以看到NonfairSync和FairSync都继承自抽象......
  • ReentrantLock源码分析、LockSuppor、ReentrantReadWriteLock、锁优化的方法
    ReentrantLock类图我们看一下重入锁ReentrantLock类关系图,它是实现了Lock接口的类。NonfairSync和FairSync都继承自抽象类Sync,在ReentrantLock中有非公平锁NonfairSync和公平锁FairSync的实现。在重入锁ReentrantLock类关系图中,我们可以看到NonfairSync和FairSync都继承自抽象......
  • [MIT 6.S081] Lab: xv6 lazy page allocation
    Lab:xv6lazypageallocationEliminateallocationfromsbrk()这一步比较简单,直接在sys_sbrk中将分配内存改为对内存大小进行修改而不分配内存即可。uint64sys_sbrk(void){intaddr;intn;if(argint(0,&n)<0)return-1;addf=myproc()->sz;my......
  • 尝试通过Codeblocks编译Codeblocks
    最近工作安排上的空余时间比较多.尝试了下通过Codeblocks去编译(Self-host)Codeblocks还传了个Gitee(code-blocks-mint),不知道后面会不会继续对其进行修改——主要最近习惯了使用qml这种脚本化的界面实现方式,看见widget跟页面标签就一阵头大;另外,Codeblocks的代码虽然相对屎......
  • 页面中的blockShow组件展示,可进行相关的样式修改,一般月饼图搭配使用,具体根据实际来
    <template><!--这是新版的相对应的颜色列表的UI--><divclass="bllockListShow"><divclass="pieList"v-for="(item,index)indataArr":key="index"@click="clickUptown(index,item)"......
  • xv6book阅读 chapter3
    页表是硬件提供进程间隔离的方法之一,并通过它来实现虚拟地址和物理地址之间的转换,通过页表可以决定进程能够访问物理内存的哪些部分,xv6提供了一些小技巧,比如在不同的地址空间中可以映射相同的trampolinepage,trampoline是用来辅助用户模式进入内核模式的,所以它可被共用。1分页硬......