首页 > 其他分享 >[MIT 6.S081] Lab: Copy-on-Write Fork for xv6

[MIT 6.S081] Lab: Copy-on-Write Fork for xv6

时间:2024-02-11 17:11:25浏览次数:30  
标签:Fork uint64 pte page Lab xv6 pa PTE 页面

Lab: Copy-on-Write Fork for xv6

在这个实验中,我们要为 xv6 实现 cow fork

Implement copy-on write

根据写时复制的方式,我们在复制页面的时候应该采用的是将父级的物理页面映射到子级的页面,因此我们需要修改 kernel/vm.c 中的 uvmcopy ,将页面复制修改为映射的方式,同时应当将两者的 PTE 中的 PTE_W 清除掉,表示为 COW 页面。

不过,在此之前,我们最好标记一下页面是不是 COW page ,这样会对页面识别很有帮助。根据 hint 2 ,我们应该在 RSW 中标记这个位。来到标记 PTE 的宏定义,添加一个 COW 页面标记。

// kernel/riscv.h
#define PTE_V (1L << 0) // valid
#define PTE_R (1L << 1)
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // 1 -> user can access
#define PTE_COW_PAGE (1L << 8)  // copy on write page

虽然按照提示应该先修改的是 uvmcopy ,不过我们现在需要先为 COW Page 提供一下基础设施。对于 COW page ,我们除了要知道一个页面是否为 COW Page ,也要知道 COW Page 该如何被构造和析构的。对于一个 COW Page 页面,会有一些进程引用它,那么谁一旦修改了这个页面,它应当和这个页面没关系了,即:谁修改,谁拷贝一份然后独立出去。那么,什么时候这个页面会被回收呢?自然是没人用的时候,只要没有进程引用它,那么这个页面就是垃圾内存了,可以让操作系统来回收它。

因此,我们需要维护一下每个页面被引用的次数,不管它是不是 COW Page

// kernel/kalloc.c
struct {
  struct spinlock lock;
  uint64 ref_count;
} cow_page_ref_info[PHYSTOP / PGSIZE];

接着,我们要知道每个地址对应的页面是哪一个。

// kernel/kalloc.c
uint64 get_cow_page_index(void* pa) {
  return ((uint64)pa - (uint64)end) / PGSIZE;
}

我们需要调整引用次数,这里就写一个函数,将这个功能独立出来。要注意的是,由于对内存的访问和修改存在竞争的问题,因此对于每个引用次数都要加锁来保证没有出错。

// kernel/kalloc.c
uint64 adjust_cow_page_ref_count(uint64 pa, int dx) {
  int idx = get_cow_page_index((void*)pa);

  uint64 cnt;
  acquire(&cow_page_ref_info[idx].lock);
  cow_page_ref_info[idx].ref_count += dx;
  cnt = cow_page_ref_info[idx].ref_count;
  release(&cow_page_ref_info[idx].lock);

  return cnt;
}

现在我们还需要对这些进行初始化,直接修改 freerange 函数,让它进行初始化。为什么这里初始化要为 1 ,因为接下来要做的是 kfree

// kernel/kalloc.c
void
freerange(void *pa_start, void *pa_end)
{
  char *p;
  p = (char*)PGROUNDUP((uint64)pa_start);
  for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE) {
    initlock(&cow_page_ref_info[get_cow_page_index(p)].lock, "cow_page_ref_count lock");
    cow_page_ref_info[get_cow_page_index(p)].ref_count = 1;
    kfree(p);
  }
}

既然引入了 COW 机制,那么对于所有的页面的分配和释放就需要做出修改。对于 kalloc 来说,分配一个新的页面后,直接将引用计数修改为 1 就好了。但是对于 kfree 来说,由于涉及到引用减少的问题,就稍微比较复杂了点。

  • 若当前页面引用计数大于 1 ,那么就不能释放它,因为它还在被其他进程所需要;
  • 若当前页面引用计数就是 1 ,那么直接释放就好了。
// kernel// kernel/kalloc.c
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.
  uint64 cnt = adjust_cow_page_ref_count((uint64)pa, -1);
  if (cnt > 0) {
    return;
  }

  memset(pa, 1, PGSIZE);
  r = (struct run*)pa;

  acquire(&kmem.lock);
  if (cnt == 0) {
    r->next = kmem.freelist;
    kmem.freelist = r;
  }
  release(&kmem.lock);
}

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

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r) {
    kmem.freelist = r->next;
  }
  release(&kmem.lock);

  if(r) {
    memset((char*)r, 5, PGSIZE); // fill with junk
    if (adjust_cow_page_ref_count((uint64)r, 1) != 1) {
      panic("kalloc: ref cnt is not 1\n");
    }
  }
  return (void*)r;
}

那么现在就出现了一个问题:对于引用计数,有减少的,有初始化为 1kalloc) 的,增加的呢?

对于增加的情况,我们思考一下,什么时候一个页面会被多个进程引用?自然是产生一个子进程的时候复制页面,也就是指向了 uvmcopy

来到 uvmcopy ,我们将其复制页面的步骤修改为对页面的引用,不要忘记了修改后对原来页面的引用计数要增加 1 ,同时要记得将 PTE 设置为不可写和标记为 COW Page

// kernel/vm.c
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags;
  // char *mem;

  for(i = 0; i < sz; i += PGSIZE){
    if((pte = walk(old, i, 0)) == 0)
      panic("uvmcopy: pte should exist");
    if((*pte & PTE_V) == 0)
      panic("uvmcopy: page not present");
    pa = PTE2PA(*pte);
    *pte = (*pte & (~PTE_W)) | PTE_COW_PAGE;
    flags = PTE_FLAGS(*pte);
    if (mappages(new, i, PGSIZE, (uint64)pa, flags) != 0) {
      goto err;
    }
    adjust_cow_page_ref_count(pa, 1);
  }
  return 0;

 err:
  uvmunmap(new, 0, i / PGSIZE, 1);
  return -1;
}

现在我们处理好引用计数这一块的东西了,我们现在要处理一下什么时候会触发 COW Page 拷贝一份然后自己独立出去的问题,也就是向 COW Page 写入的问题。这么尝试,将会出现写入页面错误的中断,因此回到 usertrap 这边,我们要写一下如何应对 15 号中断的问题。

我们要做的,就是在这个情况下,真正的申请一片页面,然后将原来的页面拷贝一份过来,并设置一下 PTE 为可写的,重新映射一下这个新的地址就好了,如果这个页面引用计数只有 1 ,那么直接修改它就好了。要注意的是,如果这里不先将 pte 设置为无效的话,会出现 remap 的问题,但是这里我们需要再一次 map

// kernel/trap.c
void usertrap() {
  ...
  } else if (r_scause() == 15) {
    if (cow_page_alloc(p->pagetable, r_stval()) != 0) {
      p->killed = 1;
    }
  } else {
  ...
}

// kernel/vm.c
int cow_page_alloc(pagetable_t pagetable, uint64 va) {
  // printf("cow_page_alloc\n");
  
  if (va >= MAXVA) {
    return -1;
  }

  pte_t *pte = walk(pagetable, va, 0);
  if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_COW_PAGE) == 0) {
    printf("cow_page_alloc: pte cannot be used\n");
    return -1;
  }

  va = PGROUNDDOWN(va);
  pte = walk(pagetable, va, 0);

  if (adjust_cow_page_ref_count(PTE2PA(*pte), 0) == 1) {
    *pte = (*pte & (~PTE_COW_PAGE)) | PTE_W;
    return 0;
  }

  uint64 src_pa = PTE2PA(*pte);
  uint64 flags = (PTE_FLAGS(*pte) & (~PTE_COW_PAGE)) | PTE_W;
  
  uint64 d_pa = (uint64)kalloc();
  if (d_pa == 0) {
    return -1;
  }
  memmove((void *)d_pa, (const void *)src_pa, PGSIZE);

  *pte = *pte & (~PTE_V);
  if (mappages(pagetable, va, PGSIZE, d_pa, flags) != 0) {
    return -1;
  }
  
  kfree((void*)PGROUNDDOWN(src_pa));

  return 0;
}

最后,我们还要稍微设置一下一个地方:copyout 。这个函数运行在内核态,是无法触发 usertrap 的,因此我们需要单独修改一下它。简单来说,就是它要写入的页面,我们先判断一下这个页面是不是 COW Page 页面,是的话给它分配一个新的就行了。

int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    if (va0 >= MAXVA) {
      return -1;
    }
    pte_t *pte = walk(pagetable, va0, 0);
    if (pte == 0
    || (*pte & (PTE_V)) == 0
    || (*pte & (PTE_U)) == 0) {
      return -1;
    }

    if ((*pte & (PTE_COW_PAGE))) {
      if (cow_page_alloc(pagetable, va0) != 0) {
        return -1;
      }
    }
    pa0 = PTE2PA(*pte);

    // pa0 = walkaddr(pagetable, va0);
    // if(pa0 == 0)
    //   return -1;
    n = PGSIZE - (dstva - va0);
    if(n > len)
      n = len;
    memmove((void *)(pa0 + (dstva - va0)), src, n);

    len -= n;
    src += n;
    dstva = va0 + PGSIZE;
  }
  return 0;
}

Grade

标签:Fork,uint64,pte,page,Lab,xv6,pa,PTE,页面
From: https://www.cnblogs.com/FlandreScarlet/p/18013406

相关文章

  • 水杉在极狐GitLab 的 DevOps 实践
    作者:华东师范大学数据学院陈烨如果看图文不过瘾,可以观看视频版背景项目我是来自于华东师范大学数据学院的,我们学院一直非常重视计算机和教育之间相结合,水杉就是我们诸多探索的其中之一。跟很多软件的落地一样,我们水杉也是经历过了从几个人的小规模开发到目前的几十个人开发......
  • 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时,将父子进程的页表项都设置为只度,当发生写错误时,在处......
  • Fork - 进程管理中一个重要的函数
    思考问题:当首次调用新创建进程时,其入口在哪里?系统调用fork函数是如何创建进程的?系统调用exec系列函数是如何更换进程的可执行代码的?1.1进程是如何创建的?在Linux系统启动时,最早通过init_task产生的进程是idle进程,也称为swapper进程,其pid为0。该进程会调用......
  • 快速上手极狐GitLab设计管理功能
    什么是设计管理功能设计管理是极狐GitLab议题功能内的一个模块,在这里设计师可以上传议题相关的设计资产,包括线框图、原型图等。与议题相关的研发、产品等职能的同事可以在议题内对设计进行浏览及协作,可以通过这种方式将设计也提供了单一可信源(SSOT)上的协作设计方法。您可以......
  • GitLab--安装部署
    配置信息系统:centos7.8gitlab版本:12.8.8 1 下载gitlabwgethttps://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-12.8.8-ce.0.el7.x86_64.rpmyum-yinstallgitlab-ce-12.8.8-ce.0.el7.x86_64.rpm 2 修改配置文件(修改前先备份)[root@localhost~]#cp/etc......
  • [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......
  • MIT 6.1810 Lab: traps
    lab网址:https://pdos.csail.mit.edu/6.828/2022/labs/traps.htmlxv6Book:https://pdos.csail.mit.edu/6.828/2022/xv6/book-riscv-rev3.pdf写前思考我们都知道,在用户进程执行系统调用时,内核代替用户进程执行,那么这种代替是一种什么样的代替。这个过程是还属于用户进程,只是执......
  • 极狐 GitLab 和 Xcode Cloud 集成,实现 iOS 的自动打包
    一直以来,iOS/macOS开发者面临一个难题:大部分云厂商只提供Linux/Windows服务器,而不提供Mac,如果想实现「持续集成自动打包」就需要绑定自己的Mac作为构建机。如果用个人Mac,一旦关机,小组同事就无法构建;如果再买一台公共Mac,又造成浪费。2022年6月,Apple在WWDC(全球开发者......
  • Nand2tetris Part1 lab04-lab06
    概述在前三个lab中,我们构建了hack计算机所需要的基础的芯片。后三个lab则是教我们如何使用这些芯片去搭建一个hack计算机并且使用汇编语言。Week04&Week05把这两个lab放在一起是因为他们给我的感觉更像是知识的互补。lab05教会你汇编语言的规范是什么,lab06则是......
  • MATLAB快速参考
    原文GitHub-MATLAB-cheat-sheet,本文做了翻译和修改MATLAB即MatLABoratory(一做坐一天坐垫实验室)MATrixLABoratory(矩阵实验室)。是一种常用于工程和数学的强大软件,也是一门语言。注意:MATLAB中的函数基本有多种不同参数的重载,本手册只写其中比较常用的方法,实际使用时可以善......