6.S081 lab6 cow
#Copy-On-Write
-
当调用
fork创建子进程时,会调用uvmcopy根据父进程的页表把父进程的内存空间拷贝至子进程。修改uvmcopy使此时父子进程共用物理页,并且关闭物理页的PTE_W标志,开启PTE_COW标志便于触发页面错误。代码如下: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); *pte &= ~PTE_W; *pte |= PTE_COW; if (mappages(new, i, PGSIZE, pa, PTE_FLAGS(*pte)) != 0){ goto err; } int idx = pa / PGSIZE; cnt[idx]++; /* 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; }上述代码的
19~20行操作了全局cnt数组,定义如下:
该数组为所有物理页的引用计数,当某个物理页被使用时,对应的引用计数值+1,用物理地址/
PGSIZE得到索引值。 -
在
usertrap中处理页面错误。调用copy_on_write函数,该函数为进程分配一个新物理页,并且将之前共享的物理页内容拷贝到新物理页中。若分配失败返回-1。void usertrap(void) { ... 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 (r_scause() == 13 || r_scause() == 15){ uint64 va = r_stval(); if (copy_on_write(p, va) != 0){ p->killed = 1; } } ... }//cow函数,成功分配物理内存则返回0,否则返回-1 int copy_on_write(struct proc *p, uint64 va){ if (va >= MAXVA){ return -1; } pte_t *pte = walk(p->pagetable, va, 0); if (pte == 0){ return -1; } if ((*pte & PTE_V) == 0 || (*pte & PTE_U) == 0){ return -1; } if ((*pte & PTE_COW) == 0){ return -1; } char *mem = (char*)kalloc(); if (mem == 0){ printf("cow: kalloc failed\n"); return -1; } memset(mem, 0, PGSIZE); *pte |= PTE_W; uint64 pa = PTE2PA(*pte); memmove(mem, (char*)pa, PGSIZE); // 重新添加映射关系 *pte = PA2PTE((uint64)mem) | PTE_FLAGS(*pte); // 由于已经分配了一个新物理页,应该使旧物理页的引用计数减1 kfree((void*)pa); return 0; }函数的
3~15行检查了一些非法情况,注意13行处检查了物理页的PTE_COW标志,假如未开启该标志,说明这不是一个COW物理页,va是一个非法虚拟地址。为进程分配新的物理页后,需要将旧的物理页拷贝至新的物理页,并且重新添加映射关系。30行调用了kfree函数,但此处并不是真正释放物理页。 -
copyout作用是把内核的数据复制到用户进程中,由于该函数是在内核中被调用的,并不会触发usertrap。修改copyout,使其能够处理COW物理页。int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) { uint64 n, va0, pa0; while(len > 0){ va0 = PGROUNDDOWN(dstva); if (va0 >= MAXVA){ return -1; } pte_t *pte = walk(pagetable, va0, 0); if (pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0){ return -1; } if ((*pte & PTE_COW) != 0){ if (copy_on_write(myproc(), va0) != 0){ return -1; } } pa0 = PTE2PA(*pte); /* 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; } -
更改
kfree函数。由于加入了COW机制,使得某些物理页被共享了,所以释放物理页的时机尤为重要。当对某个物理页调用kfree时,说明该物理页不会再被用到,应该对其的引用计数-1。如果引用计数值为0,则没有任何进程使用该物理页,此时应该回收。kfree(void *pa) { ... acquire(&kmem.lock); int idx = (uint64)pa / PGSIZE; if (--cnt[idx] == 0){ /* printf("%p\n", (uint64)pa); */ // Fill with junk to catch dangling refs. memset(pa, 1, PGSIZE); r->next = kmem.freelist; kmem.freelist = r; } release(&kmem.lock); }

浙公网安备 33010602011771号