MIT 6.1810: Lab cow: Copy-on-Write Fork for xv6

只有一个task,实现copy-on-write fork

uvmcopy

在调用fork将父进程的页表复制给子进程时,❌(原版xv6)分配新的物理页

将同样的物理页映射到子进程的页表中,设置父进程和子进程对应的PTE_W为0,这样两个进程共享一个只读页

我们将RSW中的一位(这里选择#define PTE_COW (1L << 8))记为COW位,我们将这两个PTE的COW位设为1

c 复制代码
// File: kernel/vm.c
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
	// ... (需注释掉部分代码)
	// COW
    if((*pte & PTE_W) != 0){
      *pte = *pte & ~PTE_W;   // set PTE_W to 0
      *pte = *pte | PTE_COW;  // set PTE_COW to 1
    }
    flags = PTE_FLAGS(*pte);
    if(mappages(new, i, PGSIZE, pa, flags) != 0){
      goto err;
    }
    cowcnt((void*)pa);
  }
  return 0;
}

vmfault

接下来,每当我们需要对COW标记的页执行写操作时

1.kalloc()分配一个新的物理页

2.将要执行写操作的页的物理内存复制到新的物理页上

3.修改flags位,设PTE_W = 1, PTE_COW = 0

4.将新的物理页映射到当前进程的页表中

c 复制代码
// File: kernel/vm.c
uint64
vmfault(pagetable_t pagetable, uint64 va, int read)
{
	// ...
	// COW
  if(iscow(pagetable, va)){
    pte_t *pte = walk(pagetable, va, 0);
    uint64 pa = PTE2PA(*pte);
    uint flags = (PTE_FLAGS(*pte) | PTE_W) & ~PTE_COW;
    mem = (uint64) kalloc();
    if(mem == 0){
      p->killed = -1;
      return 0;
    }
    memmove((void*)mem, (void*)pa, PGSIZE);
    kfree((void*)pa);
    *pte = PA2PTE(mem) | flags;
  }else{
    if(ismapped(pagetable, va))
      return 0;
    mem = (uint64) kalloc();
    if(mem == 0)
      return 0;
    memset((void *) mem, 0, PGSIZE);
    if (mappages(p->pagetable, va, PGSIZE, mem, PTE_W|PTE_U|PTE_R) != 0) {
      kfree((void *)mem);
      return 0;
    }
  }
}

cowcnt

我们还需要对每个物理页记录其被进程页表引用的次数

c 复制代码
// File: kernel/kalloc.c
#define MXPG (32*1024)	// 128*1024*1024 / PGSIZE

struct {
  struct spinlock lock;
  struct run *freelist;
  int cowcnt[MXPG];
} kmem;

对于物理地址为pa的物理页,cowcnt (pa - 0x80000000) / 4096 记录了当前物理页被引用的次数

每次调用kalloc时,当前物理页被引用的次数为1

c 复制代码
// File: kernel/kalloc.c
void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r){
    kmem.freelist = r->next;
    // COW
    int index = ((uint64)r - (uint64)end) / 4096;
    kmem.cowcnt[index] = 1;
  }
  release(&kmem.lock);

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

每次调用kfree时,我们需要判断当前物理页的cowcnt

cowcnt > 1 说明有不止一个页调用它,我们cowcnt--后return (用kfree实现减少一次物理页引用次数的效果)

cowcnt = 1 说明这次kfree正是要释放这个物理页

cowcnt = 0 说明处于kinit内存初始化阶段,不要处理cowcnt

c 复制代码
// File: kernel/kalloc.c
void
kfree(void *pa)
{
	// ...
	// COW
  int index = ((uint64)pa - (uint64)end) / 4096;
  if(kmem.cowcnt[index] > 0){
    if(--kmem.cowcnt[index] > 0){
      return;
    }
  }
}

我们还要实现一个函数,调用它时让pa对应物理页cowcnt + 1

每次通过uvmcopy()实现fork时,调用一次该函数(uvmcopy相关实现见上文代码)

c 复制代码
// File: kernel/kalloc.c
// set cowcnt of physical page referring to pa
// n == 1 -> increment
void
cowcnt(void* pa){
  acquire(&kmem.lock);
  int index = ((uint64)pa - (uint64)end) / 4096;
  kmem.cowcnt[index]++;
  release(&kmem.lock);
}

others

还要注意,若在遇到COW页的缺页中断时发现内存不够,杀死当前进程p->killed = 1

这一点在vmfault和copyout中均要实现

分别负责usertrap和文件系统遇到COW时的处理

(vmfault相关实现见上文代码)

c 复制代码
// File: kernel/vm.c
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
	// ...
	if((*pte & PTE_W) == 0){
      if(*pte & PTE_COW){
        if((pa0 = vmfault(pagetable, va0, 0)) == 0){
          myproc()->killed = 1;
          return -1;
        }
      }else{
        return -1;
      }
    }
}

最后的通过截图是久违的AC!

相关推荐
呆萌很21 分钟前
英语 1‑12 月单词联想记忆
笔记
金立基包装胶水2 小时前
纸袋热封频繁不良,先排查胶料这一堆问题
大数据·笔记·其他
常驻客栈2 小时前
B-深入理解计算机系统第3版之第11章:网络编程
开发语言·笔记·学习笔记·常驻客栈·深入理解计算机系统
血小板要健康3 小时前
网格 dfs 与 FloodFill:从岛屿、区域到搜索路径
笔记·算法·leetcode·深度优先
NoteDeep 深度笔记3 小时前
Observer: 思维 ≠ 自己
笔记
米饭不加菜3 小时前
一些常用模块简介
经验分享·笔记·嵌入式硬件
sunshine22 girl4 小时前
Angular7,9,学习笔记三 dom操作
笔记·学习·angular
小+不通文墨5 小时前
使用vscode里的U8G2 库编写OLED菜单控制
经验分享·笔记·单片机·嵌入式硬件·学习
葡萄成熟时 !6 小时前
泛型知识笔记
开发语言·笔记·python
北风toto6 小时前
扩展操作码技术 · 详细笔记
运维·服务器·笔记·软件设计师