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!

相关推荐
是上好佳佳佳呀5 分钟前
【LangChain|Day03】LangChain 链式调用 Chains 笔记
笔记·langchain
闪闪发亮的小星星1 小时前
轨道六根数
笔记
aaaameliaaa2 小时前
C语言随机数函数使用全解析
c语言·笔记
Cloud_Shy6182 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第六章 Item 40 - 43)
android·开发语言·人工智能·笔记·python·学习方法
chase。2 小时前
【学习笔记】Dexora:面向高自由度双臂灵巧操作的开源 VLA 系统
笔记·学习
風清掦2 小时前
【STM32学习笔记-15】FLASH 闪存(Claude)
笔记·stm32·单片机·嵌入式硬件·学习
chase。3 小时前
【学习笔记】Unified World Models:基于视频-动作耦合扩散的机器人预训练新范式
笔记·学习·音视频
影寂ldy4 小时前
C# 事件完整学习笔记(发布订阅 + 自定义事件 + 内置 EventHandler)
笔记·学习·c#
海绵宝宝的月光宝盒5 小时前
6-机械设计基础物理知识
经验分享·笔记·其他·职场和发展·课程设计·学习方法
闪闪发亮的小星星5 小时前
卫星通信、主要业务类型、组成
笔记