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!

相关推荐
砚凝霜6 分钟前
软考网络工程师|第 6 章 应用层安全协议、防火墙完整备考笔记
网络·笔记·安全
way_hj2 小时前
Flask笔记(2)快速web项目
笔记·flask·vue·web
宵时待雨2 小时前
linux笔记归纳14:Socket编程TCP
linux·服务器·网络·笔记·tcp/ip
什仙3 小时前
Maxwell:提取实体表面,用于扫掠。
笔记·学习·ansys·maxwell
zyplayer-doc4 小时前
核心制度不该被随手修改:用zyplayer-doc锁定文档和全部子文档
大数据·数据库·人工智能·笔记·pdf·ocr
啄缘之间5 小时前
5.1 uvm‑component 的层级结构管理? new (name,parent) 参数
开发语言·笔记·学习·ic·uvm·sv
江屿风6 小时前
【科普】【差集&交集概念落地云相册】流食般投喂
开发语言·c++·笔记·算法·云相册
一米阳光866115 小时前
软考(中级)软件设计师核心笔记(6)系统开发基础——需求分析、系统设计
笔记·职场发展·需求分析·软考·软件设计师·中级职称
雨田言炎19 小时前
四、关于Qt项目需要知道的
开发语言·笔记·qt
hanlin0320 小时前
动态规划专练:力扣第121、122题
笔记·算法·leetcode