代码编织梦想

这个是2021版本。

2021版本和2020版本的这部分略有不同。

COW想必科班的同学应该不陌生,因此本文注重操作过程,没有很多的分析,详细内容可参考https://pdos.csail.mit.edu/6.828/2021/labs/cow.html

下面开始做实验。

切换到COW分支

git fetch
git checkout cow
make clean

首先我们看一下fork的函数在干什么(该函数在kernel/proc.c中):

int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }

  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;

  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);

  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;

  // increment reference counts on open file descriptors.
  for(i = 0; i < NOFILE; i++)
    if(p->ofile[i])
      np->ofile[i] = filedup(p->ofile[i]);
  np->cwd = idup(p->cwd);

  safestrcpy(np->name, p->name, sizeof(p->name));

  pid = np->pid;

  release(&np->lock);

  acquire(&wait_lock);
  np->parent = p;
  release(&wait_lock);

  acquire(&np->lock);
  np->state = RUNNABLE;
  release(&np->lock);

  return pid;
}

可以发现,写时复制主要改的是uvmcopy这个函数,将本来的直接复制改成引用计数的模式。

因此,整个实验步骤如下:

先在kernel/riscv.h中加入

#define PTE_C (1L << 8) // COW

不熟悉PTE的话可以回顾一下前面做过的实验。

然后在kernel/kalloc.c的最开始的部分加入一下内容:

note: 因为到这里还没涉及多线程,测试点的部分里也没有,因此没有加锁的部分。如果是多线程则应该要上锁,毕竟这个引用计数是个共享变量。

//KERNBASE == 0x80000000
static int reference_count[(PHYSTOP - KERNBASE) / PGSIZE];

static int idx_rc(uint64 pa){ //get reference cnt
    return (pa - KERNBASE) / PGSIZE;
}
void add_rc(uint64 pa){ // add
    reference_count[idx_rc(pa)]++;
}
void sub_rc(uint64 pa){ // sub
    reference_count[idx_rc(pa)]--;
}

然后在kernel/vm.c中修改uvmcopy函数。

函数原来是:

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);
    flags = PTE_FLAGS(*pte);
    if((mem = kalloc()) == 0)
      goto err;
    memmove(mem, (char*)pa, PGSIZE);
    if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
      kfree(mem);
      goto err;
    }
  }
  return 0;

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

现在不应该直接拷贝页,而是增加引用计数,改成如下:

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags;

  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_C;
    flags = PTE_FLAGS(*pte);
    if(mappages(new, i, PGSIZE,pa, flags) != 0){
      goto err;
    }
    add_rc(pa);// 不分配页,改成增加引用计数
  }
  return 0;

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

那么在解引用的时候,需要减去一次引用计数,这部分我们需要注意一下,这个引用是逻辑上是在物理地址的位置,所以只要更改uvmunmap中的do_free有关的部分就行,即修改kfree函数(在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.
  memset(pa, 1, PGSIZE);

  r = (struct run*)pa;

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

改成如下,增加一下只有引用计数为才处理,否则就减去一次引用计数:

void
kfree(void *pa)
{
  struct run *r;

  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");
  if(reference_count[idx_rc((uint64)pa)] > 1){// > 1 just sub 1
      sub_rc((uint64)pa);
      return;
  }
  // Fill with junk to catch dangling refs.
  memset(pa, 1, PGSIZE);

  r = (struct run*)pa;

  acquire(&kmem.lock);
  r->next = kmem.freelist;
  kmem.freelist = r;
  release(&kmem.lock);
  reference_count[idx_rc((uint64)pa)] = 0;
}

下面修改usertrap函数(在kernel/trap.c中)

原来代码:

void
usertrap(void)
{
  int which_dev = 0;

  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");

  // send interrupts and exceptions to kerneltrap(),
  // since we're now in the kernel.
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();

  // save user program counter.
  p->trapframe->epc = r_sepc();

  if(r_scause() == 8){
    // system call

    if(p->killed)
      exit(-1);

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    p->trapframe->epc += 4;

    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    intr_on();

    syscall();
  } else if((which_dev = devintr()) != 0){
    // ok
  } else {
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
    printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
    p->killed = 1;
  }

  if(p->killed)
    exit(-1);

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2)
    yield();

  usertrapret();
}

现在需要增加当scause为13或15时的处理

改完后的代码为:

void
usertrap(void)
{
  int which_dev = 0;

  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");

  // send interrupts and exceptions to kerneltrap(),
  // since we're now in the kernel.
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();

  // save user program counter.
  p->trapframe->epc = r_sepc();

  if(r_scause() == 8){
    // system call

    if(p->killed)
      exit(-1);

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    p->trapframe->epc += 4;

    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    intr_on();

    syscall();
  } else if((which_dev = devintr()) != 0){
// ok
} else {
      uint64 va = PGROUNDDOWN(r_stval());
      if(va >= MAXVA){
          p->killed = 1;
          exit(-1);
      }
      pte_t* pte = walk(p->pagetable,va,0);
      if(pte == 0){
          p->killed = 1;
          exit(-1);
      }
      uint64 pa = PTE2PA(*pte);
      uint64 flag = PTE_FLAGS(*pte);
      if((r_scause() == 13 || r_scause() == 15) && (flag & PTE_C) ){
          char* mem = kalloc();
          if(mem == 0){
              printf("here\n");
              p->killed = 1;
              exit(-1);
          }
          memmove(mem,(char*)pa,PGSIZE);
          kfree((char*)pa);
          *pte = PA2PTE(mem) | (flag & ~PTE_C) | PTE_W;
      }
      else{
          printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
          printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
          p->killed = 1;
      }
  }

  if(p->killed)
    exit(-1);

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2)
    yield();

  usertrapret();
}

这部分内容就是 分配页面,并且修改标志位,同时调用kfree进行处理,减少一次引用计数。

然后修改kernel/vm.c的copyout函数。

原来代码为:

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

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    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;
}

现在改为:

int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0, flag;
  pte_t* pte;
  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    if(va0 >= MAXVA)
        return -1;
    pte = walk(pagetable,va0,0);
    if(pte == 0)
        return -1;
    pa0 = PTE2PA(*pte);
    flag = PTE_FLAGS(*pte);
    if(flag & PTE_C){
        char* mem = kalloc();
        if(mem == 0){
            return -1;
        }
        memmove(mem,(char*)pa0,PGSIZE);
        kfree((char*)pa0);
        *pte = PA2PTE((uint64)mem) | (flag & ~PTE_C) | PTE_W;
        pa0 = (uint64)mem;
    }
    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;
}

别忘了更改kalloc函数,位于(kernel/kalloc.c中)

原来为:

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
  return (void*)r;
}

现在需要在分配页的时候初始化引用计数(设置为1)

也就是:

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(r)
    reference_count[idx_rc((uint64)r)] = 1;
  return (void*)r;
}

最后在kernel/defs.h中添加声名:

// kalloc.c
void            add_rc(uint64);
void            sub_rc(uint64);

// vm.c
pte_t*          walk(pagetable_t, uint64,int);

这是最后的实验结果:

783a756e458351d2bf016e16941d364d.png

参考文献:

https://github.com/jlu-xiurui/MIT6.S081-2021-FALL/tree/master/lab6-cow

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_52877213/article/details/128754874

MIT-6.s081-OS lab cow: Copy-on-Write Fork for xv6-爱代码爱编程

代码:https://github.com/RedemptionC/xv6-riscv-6s081/tree/cow 本实验仍然是虚拟内存,所以暂且跳过了中间的内容(指lec和课本) 分析 copy on write fork : 在之前的code walk through(https://blog.csdn.net/Redemption

Mit6.S081学习记录-爱代码爱编程

Mit6.S081学习记录 前言一、课程简述二、课程资源1,课程主页2,参考书3,实验环境三、学习过程Mit6.S081-实验环境搭建Mit6.S081-GDB使用Mit6.S081-xv6参考书翻译[Mit6.S081-实验1-Xv6 and Unix utilities](https://blog.csdn.net/u013577996/art

Mit6.S081-实验1-Xv6 and Unix utilities-爱代码爱编程

Mit6.S081-实验1-Xv6 and Unix utilities 前言一、Boot xv61,实验目的2,操作流程1)切换到xv6-labs-2020代码库的lab1分支2)启动xv63)测试xv64)过程分析5)其他操作二、在xv6中添加一个自己编写的程序1,源码准备2,编译配置3,测试添加程序4,过程分析三、xv6中shell简析四、p

6.s081 Lab:Copy-on-Write Fork for xv6-爱代码爱编程

Lab:Copy-on-Write Fork for xv6 1.实验目的 在xv6内核中实现copy-on-write-fork。 2.实验内容 xv6中的fork()系统调用将父进程的所有用户空间内存复制到子进程中。如果父对象很大,复制可能需要很长时间。此外,副本通常会浪费内存;在许多情况下,父级或子级都不会修改页面,因此原则上它们可以共享相同

MIT6.S081学习总结-lab6:Copy-On-Write Fork-爱代码爱编程

lab6 也是虚拟内存的一种应用,主要实现fork时的写时复制copy-on-write功能。 问题 xv6中的fork()系统调用将所有父进程的用户空间内存复制到子进程中。如果父进程用户空间很大,复制可能需要很长时间。更糟糕的是,这种复制工作经常被浪费;例如,子进程中的fork()后跟exec()将导致子进程丢弃复制的内存,可能根本就没有使用复制来的

MIT 6.S081 lab 6:Copy-on-Write Fork-爱代码爱编程

1 Copy-on-Write Fork Lab 实现xv6 4.6节中的COW技术。 起始工作 // 1.增加标志位PTE_C的宏,表示是否为COW页 // riscv.h #define PTE_C (1L << 8) // COW page flag // 2.增添全局变量 pageCount 来对所有分配的物理页引用计

mit6.s081 lab6 Copy-on-Write Fork for xv6-爱代码爱编程

Implement copy-on write copy-on-write fork需要解决什么问题? xv6中的fork()调用需要拷贝所有父进程中的user space memory给子进程,如果user space memory中的空间很大,那么这个拷贝的过程需要消耗非常多的时间。而且这个拷贝的动作很多时候都是浪费的,因为在fork()后往往还会调

[MIT 6.S081] Lab 6: Copy-on-Write Fork for xv6-爱代码爱编程

Lab 6: Copy-on-Write Fork for xv6 Lab Guidance: https://pdos.csail.mit.edu/6.828/2020/labs/cow.htmlLab Code: https://github.com/VastRock-Huang/xv6-labs-2020/tree/cowImplement cop

MIT 6.S081 Lab6: Copy-on-Write Fork for xv6-爱代码爱编程

写在前面 这次的实验与上一个实验(懒分配)很相似,实现写时复制,实验地址在这里。与以往不同的是,这次只有一个任务,虽然是 hard 难度,但写起来并没有很吃力,应该是我写的最快的一个实验了。思路很简单,主要是容易出现各种 bug,debug 的过程会比较煎熬,。我的代码在 github 上。 在 debug 的时候,有一些我觉得没啥问题的代码出现了错误

2021 MIT6.S081 LAB5 copy-on write COW-爱代码爱编程

实验五 原网址 Lab: Copy-on-Write Fork for xv6 Implement copy-on write PTE中FLAG的第8位和第9位没用到,因此可以用来标记一个页面是不是COW的页面,额外添加一个宏定义PTE_COW uvmcopy()是fork()时将父进程的页面拷贝到子进程页面的函数,为了实现COW,取消内存拷贝,

MIT6.S081 2021-爱代码爱编程

MIT6.S081 2021 环境配置Xv6 and Unix utilitiesvscode格式化头文件排序问题以地址空间的视角看待变量其他代码参考system callstraceSysinfo lab地址 环境配置 虚拟机基本配置 下载Ubuntu镜像华为镜像站, 在software&update中修改软件源(我选的是阿里源)。

6.s081 lab5 copy-on-write fork for xv6_蔚天灿雨的博客-爱代码爱编程

6.S081 Lab5 Copy-on-Write Fork for xv6 0. 背景 + 思路 (1)背景: 当shell执行指令的时候会fork,shell子进程的第一件事就是调用exec,执行我们想要执行的命令。如果fork创建了shell地址空间的完整copy,而exec的第一件事就是丢弃这个空间,这样太浪费了 (前面的课程中讲过,通常来说

6.s081 lab 6: copy-on-write fork for xv6_巴勃罗·捏捏达的博客-爱代码爱编程

6.S081 lab 6: Copy-on-Write Fork for xv6 最近看完了xv6里有关页的内容,并且将这个部分的实验做完,第6个lab明显难度起来了,为了巩固一下学习到的知识,决定写一篇博客。 xv6课

2020 mit6.s081 lab: copy-on-write fork for xv6_codefreestyle的博客-爱代码爱编程

文章目录 实验链接实验Implement copy-on-write提交结果查看结果 常用命令Github 实验链接 https://pdos.csail.mit.edu/6.S081/2020

操作系统mit6.s081:lab1-爱代码爱编程

本系列文章为MIT6.S081的学习笔记,包含了参考手册、课程、实验三部分的内容,前面的系列文章链接如下 操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口 操作系统MIT6.S081:P1->Int

操作系统mit6.s081:p1-爱代码爱编程

本系列文章为MIT6.S081的学习笔记,包含了参考手册、课程、实验三部分的内容,前面的系列文章链接如下 操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口 文章目录 一、操作系统概述二、操

操作系统mit6.s081:lab5-爱代码爱编程

本系列文章为MIT6.S081的学习笔记,包含了参考手册、课程、实验三部分的内容,前面的系列文章链接如下 操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口 操作系统MIT6.S081:[xv6参考手册第2

操作系统mit6.s081:lab6-爱代码爱编程

本系列文章为MIT6.S081的学习笔记,包含了参考手册、课程、实验三部分的内容,前面的系列文章链接如下 操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口 操作系统MIT6.S081:[xv6参考手册第2

mit6.s081 2021 copy-爱代码爱编程

MIT6.S081 2021 Copy-on-Write Fork for xv6 简要介绍debug代码参考 简要介绍 There is a saying in computer sy