lab8 lock

lock

实验结果

实验

Memory allocator

就如hints上面提示的,一个cpu一个单向链表。
一个cpu上没有可以malloc的空间,那么用一个for循环从别的cpu链表上找可用的page。
唯一的缺点是,for循环时,你得到前面那一个链表没有用的空间时,由于你释放掉,然后获取别的cpu锁,你认为前面的cpu链表上的page是没有malloc的,但其实在那一刻是有的,下面我Buffer cache的代码也有类似问题

// Physical memory allocator, for user processes,
// kernel stacks, page-table pages,
// and pipe buffers. Allocates whole 4096-byte pages.

#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "spinlock.h"
#include "riscv.h"
#include "defs.h"

void freerange(void *pa_start, void *pa_end);

extern char end[]; // first address after kernel.
                   // defined by kernel.ld.

struct run {
  struct run *next;
};

struct _kmem{
  struct spinlock lock;
  struct run *freelist;
};

struct _kmem kmem[NCPU] = {0};

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

void
freerange(void *pa_start, void *pa_end)
{
  char *p;
  p = (char*)PGROUNDUP((uint64)pa_start);
  for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
    kfree(p);
}

// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc().  (The exception is when
// initializing the allocator; see kinit above.)
void
kfree(void *pa)
{
  struct run *r;
  int id;

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

}

// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{
  struct run *r;
  int id;

/*
  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r)
    kmem.freelist = r->next;
  release(&kmem.lock);
*/
  push_off();
  id = cpuid();

  acquire(&(kmem[id].lock));
  r = kmem[id].freelist;
  if (r)
  {
    kmem[id].freelist = r->next;
  }
  else
  {
    for(int i = 0; i < NCPU; i++)
    {
      if (i != id)
      {
        acquire(&(kmem[i].lock));
        r = kmem[i].freelist;

        if (r)
        {
          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;
}

Buffer cache

思路

可以把问题拆分成4个主要问题+一些限制条件+一些编程技巧

4个问题分别是怎么初始化,bget()里怎么查找匹配的cache block,如果不满足那么要怎么替换,怎么释放

限制条件是,应该要满足局部性原理,即最近访问的cache block被替换的可能性最低。

具体思路
对于dev,blockno,采用的哈希函数是key = (((dev % 13)^2 % 13)+blockno) % 13,具体细节可以查看GetCacheKey()函数,对于可能出现的散列冲突问题,我是采用每个hash都代表一个单链表的方式,这样就不会出现一个hash值只能对应一个元素的情况。

每个hash bucket都有一个锁,涉及这个bucket不变时,都调用这个自旋锁。然后整体上一个自旋锁,对于一个bucket没有可用块时,要去别的块偷可用块时,要使用这个全局锁

为了维护局部性,以及方便。每个bucket中的block,离head越远,就代表越近时使用。所以替换时,就从bucket的头来寻找,离头越近,就越远时使用,从一定角度上,维护了局部性。当然从别的块偷时,也是先从头开始偷。对于题目中用trap.c中的tick,发现tick是uint型,即32位,tick越大,就是越近时使用,但是32为的时候呢?1其实是最近的,但反而比其他小很多,而且假设1ms一个tick,只能支持49天左右,系统运行49天后就出现了bug?还是别用了,基于以上考虑,我就没用tick

寻找就是获得hansh key,然后对着那个hash bucket的单链表上比较dev和blockno,找到就返回。找不到就执行替换策略。

替换时,首先搜索bucket的链表。如果没有,那么再考虑从别的块上偷。

具体的策略是,上bucket锁,然后查找是否已经有块,有的话释放锁然后返回,没有的话,查看这个bucket上是否有空闲的block,有的话用这个然后释放锁,没有的话,也要释放锁。然后获取全局锁,再获取bucket锁(没找到是要先释放bucket锁,然后获取全局锁,再获取bucket锁,否则先获取bucket锁,再获取大锁,由于下面别的进程还可能用到bucekt锁,会造成死锁),然后重复查找是不是有对应的block,然后再看是不是这个bucket有ref为0的block(因为你中间释放掉bucket锁,造成条件可能改变,如不这样,会造成freeing free block),然后在从别的bucket获取锁,偷,然后释放锁。具体可以看代码

相关代码

// Buffer cache.
//
// The buffer cache is a linked list of buf structures holding
// cached copies of disk block contents.  Caching disk blocks
// in memory reduces the number of disk reads and also provides
// a synchronization point for disk blocks used by multiple processes.
//
// Interface:
// * To get a buffer for a particular disk block, call bread.
// * After changing buffer data, call bwrite to write it to disk.
// * When done with the buffer, call brelse.
// * Do not use the buffer after calling brelse.
// * Only one process at a time can use a buffer,
//     so do not keep them longer than necessary.


#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 CACHEBUCKET_NUM   (13U)

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

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 __Bucket Bucket[CACHEBUCKET_NUM];
} bcache;

static uint64 GetCacheKey(uint dev, uint blockno);
static int InsertEnd(struct buf* ListHead, struct buf* Buf);
static uint64 RemovePointFromList(struct buf* ListHead, struct buf* Buf);
static int InsertNewEnd(struct buf* ListHead, struct buf* Buf);

void
binit(void)
{
  struct buf *b;

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

  
  
  return;
}

// 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;
  uint64 HashValue = 0;
  
  HashValue = GetCacheKey(dev, blockno);
  acquire(&(bcache.Bucket[HashValue].lock));
  for(b = bcache.Bucket[HashValue].head.next; b != 0; b = b->next)
  {
    if ((b->dev == dev) && (b->blockno == blockno))
    {
      b->refcnt++;
      release(&(bcache.Bucket[HashValue].lock));
      acquiresleep(&b->lock);
      return b;
    }
  }
 for(b = bcache.Bucket[HashValue].head.next; b != 0; b = b->next)
  {
    if (b->refcnt == 0)
    {
      b->dev = dev;
      b->blockno = blockno;
      b->valid = 0;
      b->refcnt = 1;
      release(&(bcache.Bucket[HashValue].lock));
      acquiresleep(&b->lock);
      return b;
    }
  }
  release(&(bcache.Bucket[HashValue].lock));//没有在这个bucket找到对应的block


  acquire(&(bcache.lock));
  acquire(&(bcache.Bucket[HashValue].lock));
  /*
  会出现一个cpu运行完第一步没找到,卡在第二步的bucket锁,另一个cpu运行完第二步,为同样的dev和blockno再分配一块不行
  会导致一个block有两个 cache block
  */
  for(b = bcache.Bucket[HashValue].head.next; b != 0; b = b->next)
  {
    if ((b->dev == dev) && (b->blockno == blockno))
    {
      b->refcnt++;
      release(&(bcache.Bucket[HashValue].lock));
      release(&(bcache.lock));
      acquiresleep(&b->lock);
      return b;
    }
  }
 for(b = bcache.Bucket[HashValue].head.next; b != 0; b = b->next)
  {
    if (b->refcnt == 0)
    {
      b->dev = dev;
      b->blockno = blockno;
      b->valid = 0;
      b->refcnt = 1;
      release(&(bcache.Bucket[HashValue].lock));
      release(&(bcache.lock));
      acquiresleep(&b->lock);
      return b;
    }
  }
  //如果找不到空闲的,去别的链表偷
  //使用时间越远的block距离head越近。
  for(uint64 i = 0; i < CACHEBUCKET_NUM; i++)
  {
    if (i != HashValue)//自旋锁非递归锁
    {
      acquire(&(bcache.Bucket[i].lock));
      for(b = bcache.Bucket[i].head.next; b != 0; b = b->next)
      {
          if (b->refcnt == 0)
          {
            b->dev = dev;
            b->blockno = blockno;
            b->valid = 0;
            b->refcnt = 1;
            if (RemovePointFromList(&(bcache.Bucket[i].head), b) < 0)
            {
              panic("bget unkown error");
            }
            if (InsertNewEnd(&(bcache.Bucket[HashValue].head), b) < 0)
            {
              panic("bget unkown error");
            }
            release(&(bcache.Bucket[i].lock));
            release(&(bcache.Bucket[HashValue].lock));
            release(&(bcache.lock));
            acquiresleep(&b->lock);
            return b;
          }
      }
      release(&(bcache.Bucket[i].lock));
    }
  }

  release(&(bcache.Bucket[HashValue].lock));
  release(&(bcache.lock));
  //发生系统严重错误,找不到可以ref的
  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");

  uint64 HashValue = 0;
  releasesleep(&b->lock);
  HashValue = GetCacheKey(b->dev, b->blockno);

  acquire(&(bcache.Bucket[HashValue].lock));
  b->refcnt = b->refcnt - 1;
  if (b->refcnt == 0)
  {
    if (InsertEnd(&(bcache.Bucket[HashValue].head), b) < 0)
    {
      release(&(bcache.Bucket[HashValue].lock));
      panic("cache insert error");
    }
  }
  release(&(bcache.Bucket[HashValue].lock));

}

void
bpin(struct buf *b) 
{
  uint64 HashValue = GetCacheKey(b->dev, b->blockno);
  acquire(&(bcache.Bucket[HashValue].lock));
  b->refcnt = b->refcnt + 1;
  release(&(bcache.Bucket[HashValue].lock));
}

void
bunpin(struct buf *b) 
{
  uint64 HashValue = GetCacheKey(b->dev, b->blockno);
  acquire(&(bcache.Bucket[HashValue].lock));
  b->refcnt = b->refcnt - 1;
  release(&(bcache.Bucket[HashValue].lock));
}


/**
 * @哈希函数 key = ((a % 13)^2 + b)% 13
 */
static uint64 GetCacheKey(uint dev, uint blockno)
{
  uint64 Result;

  Result = dev % CACHEBUCKET_NUM;
  Result = Result * Result;
  Result = (Result + blockno) % CACHEBUCKET_NUM;

  return Result;
}

/** 
 * @brief 保证Buf一定在这条链路上
*/
static int InsertEnd(struct buf* ListHead, struct buf* Buf)
{
  if ((ListHead == 0) || (Buf == 0))
  {
    return -1;
  }
  struct buf* Temp0 = ListHead;

  if (RemovePointFromList(ListHead, Buf) < 0)
  {
    panic("illegal use InsertEnd");
  }

  Temp0 = ListHead;
  while(Temp0->next != 0)
  {
    Temp0 = Temp0->next;
  }
  Temp0->next = Buf;
  Buf->next = 0;

  return 0;
}

static uint64 RemovePointFromList(struct buf* ListHead, struct buf* Buf)
{
  if ((ListHead == 0) || (Buf == 0))
  {
    return -1;
  }
  struct buf* Temp0 = ListHead;
  struct buf* Temp1 = 0;

  while((Temp0->next != Buf) && (Temp0 != 0))
  {
    Temp0 = Temp0->next;
  } 
  if (Temp0 == 0)//上层非法调用该函数,Buf不在这条链表里
  {
    return -1;
  }
  Temp1 = Buf->next;
  Temp0->next = Temp1;
  Buf->next = 0;

  

  return 0;
}

static int InsertNewEnd(struct buf* ListHead, struct buf* Buf)
{
  if ((ListHead == 0) || (Buf == 0))
  {
    return -1;
  }
  struct buf* Temp0 = ListHead;

  while(Temp0->next != 0)
  {
    Temp0 = Temp0->next;
  }
  Temp0->next = Buf;
  Buf->next = 0;


  return 0;
}

相关问题

balloc:out of blocks

问题如下

从该网址获得解决方案图片3解决方案

即增大FSSIZE就可以解决了。

问题2:出现panic: freeing free block

可以观察到该错误应该是重复释放可以disk引起的,由于我们只修改cache层的代码,则应该是一个disk对应多个cache block引起的。所以获取全局锁,bucket锁后要,然后重复查找是不是有对应的block,然后再看是不是这个bucket有ref为0的block(因为你中间释放掉bucket锁,造成条件可能改变,如不这样,会造成freeing free block),具体思路看上面。

相关知识

定义

无论在哪种操作系统上,锁都是对某种资源同一时间只有一个拥有者的抽象,即某种资源被某个任务获取后,任务不释放该锁,其他任务无法拥有该资源。

为啥需要锁

从数电的角度,数字电路是有竞争冒险的。从计算机组成原理的角度,多个cpu同时访问一个内存会出现问题,多个任务(线程,进程,中断服务程序)同时访问一个资源时会出现问题。锁是为了解决该类问题而提出的抽象。

锁的分类

从是否等待的角度

自旋锁 睡眠锁

是否可以递归

递归锁 非递归锁

锁与中断

睡眠锁是不能在中断中使用,睡眠锁修改一些变量时需要临界区,也可能通过自旋锁或者禁用中断完成。

自旋锁可以在中断中使用,但自旋锁要禁止本cpu的抢占,如果涉及到中断上下文的访问,spin lock需要和禁止本 CPU 上的中断联合使用。如果中断时在bottom中使用自旋锁,那么在使用是要禁用disable bottom half。

锁的应用

rtos中一个上层api要调用某个硬件,下层函数也要调用硬件,那么需要递归锁。

使用原则

1:如果一个变量被一个cpu读写时,可以被另一个cpu读写,那么需要锁来保护这个变量

2:一个需要被锁保护的变量可能可以存储在多个地方,每个地方都需要锁来保护它

3:如果有几个任务可能在同一时间拥有几把锁,那么为了避免死锁,这几个任务获取锁的顺序要一致

4:如果大范围锁后,可能要用小范围的锁,大范围的锁要先获取,如本题目中的cache

原子操作

原子操作是CPU提供的一种指令,它保证原子性,即该指令不会被本(其他)CPU打断,保证该指令的原子性。

rtos中原子操作和linux中原子操作的区别

rtos一般是单核的,所以原子操作只要保证指令不被中断打断即可实现。rtos中复杂的原子操作可能要参考linux的自旋锁

linux中可能存在多个cpu,所以可能出现多个cpu同时访问读写一个内存的情况,所以linux中的原子操作不仅要保正cpu的指令不被中断打断,还要借助硬件总线上的帮助,锁住总线,让同一时间只有一个cpu可以访问读写这个内存位置

其他

如果有人读的话,祝大家新年快乐。

少喝葡萄酒+啤酒,昨晚喝到2点多,今天人都还有晕,幸好昨天就写了一大半部分文章。

posted @ 2025-01-30 14:12  我们的歌谣  阅读(68)  评论(0)    收藏  举报