MIT 6.S081 lab 8:Lock

1 Memory allocator

Xv6的user/kalloctest 程序关注于内存分配器:三个进程扩大和减小它们的地址空间,导致了很多对于kallockfree的调用,而它们都会获取kmem.lock.kalloctest会打印(像"#fetch-and-add")在acquire中尝试获得一个其他进程已经持有的锁的迭代次数,如kmem、以及一些其他的锁。acquire中的迭代次数是一种粗糙的评估锁竞争次数的方式。在你完成这个lab之前,kalloctest的输出看起来有些类似于以下:

$ kalloctest
start test1
test1 results:
--- lock kmem/bcache stats
lock: kmem: #fetch-and-add 83375 #acquire() 433015
lock: bcache: #fetch-and-add 0 #acquire() 1260
--- top 5 contended locks:
lock: kmem: #fetch-and-add 83375 #acquire() 433015
lock: proc: #fetch-and-add 23737 #acquire() 130718
lock: virtio_disk: #fetch-and-add 11159 #acquire() 114
lock: proc: #fetch-and-add 5937 #acquire() 130786
lock: proc: #fetch-and-add 4080 #acquire() 130786
tot= 83375
test1 FAIL

对于每个锁,acquire会维护一个对于这个锁的调用计数,还有acquire中尝试获得锁但是失败的次数。kalloctest调用了系统调用使得内核去打印kmembcache锁的相关计数,还有五个竞争次数最多的锁。如果发生锁竞争的话,acquire产生的迭代次数会是非常大的。这个系统调用会返回kmem和bache 锁的总的迭代次数。

对于这个实验,您必须使用专用的多核卸载机器。如果你使用了一个做其他事情的机器,那么计数将会是无意义的。你可以使用雅典娜机,或你自己的笔记本电脑,但不要使用拨号机。

kalloctest中锁争用的根本原因是kalloc()有一个由一个锁保护的空闲列表。为了移除锁竞争,你需要重新设计一个内存分配器来避免单个锁和列表。基本的想法是为每个核都维护一个空闲列表,每个列表都有一个自己的锁。不同CPU上的分配和释放可以并行,因为它们在不同的空闲列表上操作。主要的挑战会是某个CPU的空闲列表为空,而另一个是空闲的;这种情况下,那个CPU必须从其他CPU那边偷一些过来。偷内存可能需要引入锁竞争,但是这是很不平凡的。

您的工作是实现每个CPU的空闲列表,并在CPU的空闲列表为空时进行窃取。您必须给出所有以“kmem”开头的锁名。也就是说,您应该为每个锁调用initlock,并传递一个以“kmem”开头的名称。运行kalloctest,看看您的实现是否减少了锁争用。要检查它是否仍然可以分配所有的内存,请运行usertests sbrkmuch。您的输出将类似于下面所示,在kmem锁上的争用总数大大减少,尽管具体数字会有所不同。确保用户测试中的所有测试都通过。
$ kalloctest
start test1
test1 results:
--- lock kmem/bcache stats
lock: kmem: #fetch-and-add 0 #acquire() 42843
lock: kmem: #fetch-and-add 0 #acquire() 198674
lock: kmem: #fetch-and-add 0 #acquire() 191534
lock: bcache: #fetch-and-add 0 #acquire() 1242
--- top 5 contended locks:
lock: proc: #fetch-and-add 43861 #acquire() 117281
lock: virtio_disk: #fetch-and-add 5347 #acquire() 114
lock: proc: #fetch-and-add 4856 #acquire() 117312
lock: proc: #fetch-and-add 4168 #acquire() 117316
lock: proc: #fetch-and-add 2797 #acquire() 117266
tot= 0
test1 OK
start test2
total free number of pages: 32499 (out of 32768)
.....
test2 OK
$ usertests sbrkmuch
usertests starting
test sbrkmuch: OK
ALL TESTS PASSED
$ usertests
...
ALL TESTS PASSED
$

一些建议:

  • 你可以从kernel/param.h中用常量NCPU
  • 使所有空闲内存都给调用freerange的CPU。(这部分没怎么理解,就按照自己的方式做了。)
  • cpuid会返回一个当前核号,但是这个调用只在关闭中断时才正确。你应当使用push_off()和push_off()来打开或者关闭中断。
  • 看一下snprintf函数,用于字符串格式化。不过,把所有锁都命名为kmem也是可以的。

只要修改kernel/kalloc.c中的内容即可。

为每个CPU都添加一个kmem

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

添加一个全局锁,用于窃取内存时的同步

struct spinlock allLock;

修改freerange()

对于freerange的修改与提示不同,本次实现中把freerange函数用于在初始化时给每个CPU的freelist进行分配内存。窃取内存部分实现在steal函数中。

因为整个空间并不能按CPU数进行等分,所以起始和结束都使用了PGROUNDUP宏,否则会出现某页丢失的情况。

void
freerange(int cpuId,void *pa_start, void *pa_end)
{
   
   
  char *p;
  p = (char*)PGROUNDUP((uint64)pa_start);
  for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE){
   
   
      struct run *r = (struct run*)p;
      r->next = kmem[cpuId].freelist;
      kmem[cpuId].freelist = r;
  }
}

修改kinit()

这边主要就是锁的初始化以及空闲列表的分配。

void
kinit()
{
   
   
    initlock(&allLock,"all");
    uint64 partLen = (PHYSTOP - (uint64)end)>>3;
    for(uint64 i = 0;i < NCPU;i++){
   
   
        initlock(&kmem[i].lock, "kmem");
        void *st = (void *)PGROUNDUP((uint64) (end) + i*partLen);
        void *ed = (void *)PGROUNDUP((uint64) (end
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值