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!

相关推荐
RainCity5 天前
Java Swing 自定义组件库分享(十二)
java·笔记·后端
LinXunFeng12 天前
Obsidian - 使用 Share Note 分享笔记并自部署
前端·笔记·github
闪闪发亮的小星星17 天前
高斯光以及高斯光公式解释
笔记
cqbzcsq17 天前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
阿米亚波17 天前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
自传.17 天前
尚硅谷 Vibe Coding|第三章(1) Claude Code深度使用与进阶技巧 学习笔记
笔记·学习·尚硅谷·vibecoding
.千余17 天前
【C++】模板进阶全解:非类型参数|全特化|偏特化|分离编译完全指南
开发语言·c++·笔记·学习·其他
自传.17 天前
尚硅谷 Vibe Coding|第二章 AI编程工具生态 学习笔记
笔记·学习·ai编程·尚硅谷·vibe coding
秋波。未央17 天前
Java Agent 开发 · Day 1 学习笔记(含作业完整标准答案)
java·笔记·学习
中屹指纹浏览器17 天前
2026指纹浏览器字体指纹、字体渲染偏差检测与全维度虚拟字体池搭建方案
经验分享·笔记