首页 > 其他分享 >[MIT 6.S081] Lab: locks

[MIT 6.S081] Lab: locks

时间:2024-02-17 21:02:50浏览次数:29  
标签:struct lock cache MIT locks table bcache S081 id

Lab: locks

在本次实验中依然是承接上次的实验,继续多线程编程的实践。

Memory allocator

在该任务中,要为每个 CPU 实现单独的内存空闲队列分配,在该分配方式下,若单个 CPU 的空闲队列内存不够,则需要从其他 CPU 拿走一些空闲内存。

转到 kernel/kalloc.c ,我们首先为“每个 CPU 的空闲队列”提供基础。

struct run {
  struct run *next;
};

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

在添加基础以后,我们要改变原来的构造方式和析构方式。对于构造,我们要为每个锁初始化。对于析构,关机就是析构了,每次启动的时候都会重新初始化,因此不用管。

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

我们只是处理了锁的初始化,还没有解决内存的分配问题。一种简单的解决方法是让 freerange 直接分配好内存,就像还没改动之前一样,那么我们可以选择直接修改 kfree ,让它释放的内存能够回到某个 CPU 的空闲队列中,这也是要解决的内存分配与释放问题。

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

最后就是内存分配 kalloc 。我们应当遵循这个逻辑:当前内核线程是在哪个 CPU 上运行,那么这个 CPU 应当提供内存,如果它不够,那么就需要找其他 CPU 中有没有了。这样就结束了内存分配任务。

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

  push_off();
  int cpu_id = cpuid();
  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)
        continue;

      acquire(&kmem[i].lock);
      struct run *target = kmem[i].freelist;
      if (target) {
        kmem[i].freelist = target->next;
        r = target;
        release(&kmem[i].lock);
        break;
      }
      release(&kmem[i].lock);
    }
  }
  release(&kmem[cpu_id].lock);
  pop_off();

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

最后是统一要注意的点:

  • 获取 cpu_id ,需要关闭中断才有效,使用它也需要关闭中断,因此这里就需要算好作用域
  • 对哪个 cpu 进行操作,需要注意为其加锁,否则就会出现竟态
    • kalloc 要注意一下什么写法会死锁

Buffer cache

这个任务中,我们要为 buffer cache 解决一把大锁管理导致性能低下的问题。根据提示,这里可以使用哈希表作为解决目前只能一把大锁管理一个数据结构的问题。我们先将原本的结构改造为哈希表,或者改造为几个桶。那么什么东西作为哈希表的 key 呢?这里使用的是 block_no ,才能通过测试。

#define BUF_HASH_TABLE_N 13
#define BUF_HASH(BLOCK_NO) (BLOCK_NO % BUF_HASH_TABLE_N)

struct buf_hash_table {
  struct spinlock lock;
  struct buf head;
};

struct {
  // struct spinlock lock;
  struct buf buf[NBUF];
  struct buf_hash_table table[BUF_HASH_TABLE_N];
} bcache;

我们依然是解决构造和析构的问题。析构不需要考虑的原因依然是一样的。来到构造,我们需要为每个桶的锁初始化,然后将每个单独的 buf 块分配好,最简单的方式就是先分配给一个,不要忘记初始化 b->lock

void
binit(void)
{
  for (int i = 0; i < BUF_HASH_TABLE_N; i ++) {
    initlock(&bcache.table[i].lock, "bcache");
    bcache.table[i].head.prev = &bcache.table[i].head;
    bcache.table[i].head.next = &bcache.table[i].head;
  }

  for (int i = 0; i < NBUF; i ++) {
    struct buf *b = &bcache.buf[i];

    b->next = bcache.table[0].head.next;    
    b->prev = &bcache.table[0].head;
    b->last_access_tick = 0;
    initsleeplock(&b->lock, "buffer");
    bcache.table[0].head.next->prev = b;
    bcache.table[0].head.next = b;
  }
}

注意,上述代码中出现了一个新的字段:last_access_tick 。根据实验要求,我们需要实现 LRU ,因此需要添加一个字段,以记录其最后访问时间。

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;
  uint last_access_tick;
  uchar data[BSIZE];
};

对于 last_access_tick 要怎样取值,我们可以直接以 kernel/trap.c 中的 ticks 作为时间戳,只需要一个正确的访问方法即可。

uint get_current_tick() {
  uint result;
  acquire(&tickslock);
  result = ticks;
  release(&tickslock);
  return result;
}

对于时间该如何更新,我们应当在对 buffer 的访问操作中进行(不包括拿一个和归还)。更新时间操作只需要直接获取一下就可以了。要注意的是,还有一个更改引用计数的行为,此时我们就需要防止其他线程修改它,也就是要为它所在的桶加锁。

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

  b->last_access_tick = get_current_tick();

  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);
  b->last_access_tick = get_current_tick();
}

void
bpin(struct buf *b) {
  int table_id = BUF_HASH(b->blockno);

  acquire(&bcache.table[table_id].lock);
  b->refcnt ++;
  release(&bcache.table[table_id].lock);
}

void
bunpin(struct buf *b) {
  int table_id = BUF_HASH(b->blockno);

  acquire(&bcache.table[table_id].lock);
  b->refcnt --;
  release(&bcache.table[table_id].lock);
}

我们先转向释放 buffer 的操作。在这里,原先的实现是将个块作为链表头部,这样再按照原本的 get 顺序可以实现 LRU 功能。不过这里,由于我们是哈希表,而且是使用时间戳作为这个功能,因此在这里只需要直接减少引用计数即可。

// 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 table_id = BUF_HASH(b->blockno);
  acquire(&bcache.table[table_id].lock);
  b->refcnt --;
  release(&bcache.table[table_id].lock);
}

对于最后的 bget ,我们要做的只是以下操作。

  1. 查看目标桶中是否已经有了缓存
  2. 如果没有,那么当前桶是否有空的块
  3. 如果还没有,那么其他位置是否有空的块且访问时间最远古

对于这些操作,我们只需要直接按照上述顺序遍历即可。对于判断缓存是否可用,我们只需查看引用计数是否为 0 即可,对于访问时间,我们只需要比较我们自己添加的时间戳即可。

static struct buf*
bget(uint dev, uint blockno)
{
  int table_id = BUF_HASH(blockno);
  
  acquire(&bcache.table[table_id].lock);
  
  // Is the block already cached?
  for (struct buf *b = bcache.table[table_id].head.next; b != &bcache.table[table_id].head; b = b->next) {
    if (b->dev == dev && b->blockno == blockno) {
      b->refcnt ++;
      release(&bcache.table[table_id].lock);
      acquiresleep(&b->lock);
      return b;
    }
  }

  // Not cached.
  // Is the table has a free cache?
  for (struct buf *b = bcache.table[table_id].head.next; b != &bcache.table[table_id].head; b = b->next) {
    if (b->refcnt == 0) {
      b->dev = dev;
      b->blockno = blockno;
      b->valid = 0;
      b->refcnt = 1;
      // b->last_access_tick = get_current_tick();
      release(&bcache.table[table_id].lock);
      acquiresleep(&b->lock);
      return b;
    }
  }

  // Not had free cache.
  // Try move free cache from other table.
  struct buf *free_cache = 0;
  int src_id = -1;
  for (int i = 0; i < BUF_HASH_TABLE_N; i ++) {
    if (i == table_id) 
      continue;
    
    acquire(&bcache.table[i].lock);
    for (struct buf *b = bcache.table[i].head.next; b != &bcache.table[i].head; b = b->next) {
      if (b->refcnt != 0) 
        continue;
      if (free_cache == 0 || free_cache->last_access_tick > b->last_access_tick) {
        free_cache = b;
        src_id = i;
      }
    }
    release(&bcache.table[i].lock);
  }

  if (free_cache) {
    // init
    free_cache->dev = dev;
    free_cache->blockno = blockno;
    free_cache->valid = 0;
    free_cache->refcnt = 1;
    // free_cache->last_access_tick = get_current_tick();

    // take
    acquire(&bcache.table[src_id].lock);
    free_cache->prev->next = free_cache->next;    
    free_cache->next->prev = free_cache->prev;
    release(&bcache.table[src_id].lock);

    // insert
    free_cache->next = bcache.table[table_id].head.next;
    free_cache->prev = &bcache.table[table_id].head;
    bcache.table[table_id].head.next->prev = free_cache;
    bcache.table[table_id].head.next = free_cache;

    release(&bcache.table[table_id].lock);
    acquiresleep(&free_cache->lock);
    return free_cache;
  }

  // Not found.
  panic("bget: no buffers");
}

标签:struct,lock,cache,MIT,locks,table,bcache,S081,id
From: https://www.cnblogs.com/FlandreScarlet/p/18018390

相关文章

  • 在k8S中,Requests和Limits如何影响Pod的调度?
    在Kubernetes(k8S)中,requests和limits是在Pod或容器级别定义的资源限制。它们对Pod的调度和运行时行为有显著影响:Requests(请求):在Pod规范中通过resources.requests设置每个容器需要保证的基本资源量。当Kubernetes调度器为新创建的Pod选择节点时,会确保目标......
  • vue $emit事件用法
    App.vue<template> <ConpentA @paEvent="clickData"/> {{mes}}</template><script>importConpentAfrom'./components/ConpentA.vue';exportdefault{ data(){  return{   mes:''  } },......
  • [MIT 6.S081] Lab7: Multithreading
    Lab7:Multithreading在这个实验中主要是要熟悉一下多线程的一些东西,比如实现一个用户态线程,还有使用一些api。Uthread:switchingbetweenthreads这个任务的主要目的是实现用户态线程的调度,不过这个用户态线程个人认为是有栈协程。在这个任务中,需要实现在一个CPU资源的情......
  • 【XV6】 locks
    代码:https://github.com/JasenChao/xv6-labs.git内存分配器单个空闲内存列表可能引起多个CPU的频繁锁争用,题目要求设计内存分配器,让每个CPU维护一个空闲内存列表,不同CPU的分配和释放可以并行执行,但如果一个CPU可用列表为空,而其他CPU可用列表不为空,则这个CPU必须窃取其他CPU的空......
  • MIT 6.1810 Lab: Multithreading
    lab网址:https://pdos.csail.mit.edu/6.828/2022/labs/cow.htmlxv6Book:https://pdos.csail.mit.edu/6.828/2022/xv6/book-riscv-rev3.pdfschedule代码分析scheduler在内核初始化的最后调用,内核初始化由main函数承担,运行在特权模式,main函数由start函数调用,start函数运行在机器模......
  • [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......
  • form里面有多个对象,对象里面的每一项都是formItem,rules应该怎么配置
    <template><el-form:model="formData":rules="formRules"ref="form"label-width="100px"><el-form-itemlabel="对象1"><el-inputv-model="formData.object1.prop1"pla......
  • 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时,将父子进程的页表项都设置为只度,当发生写错误时,在处......
  • Omit 用于创建一个新的类型,该类型包含了原始类型`T`的所有属性,但排除了指定的键`K`
    在TypeScript中,Omit<T,K>是一个内置的实用类型(从TypeScript3.5版本开始提供),用于创建一个新的类型,该类型包含了原始类型T的所有属性,但排除了指定的键K。其定义如下:typeOmit<T,Kextendskeyofany>=Pick<T,Exclude<keyofT,K>>;这个类型的工作原理是首先找出T的所有键(......