# Implement copy-on-write fork
大意是子进程和父进程共享物理内存,只有需要对共享页写操作时再分配新的自己的物理页。
根据提示,为了区分当前页面是否是 copy-on-write 页面,在 riscv.h 中设置 PTE 保留的第 8 位作为标志。
#define PTE_C (1L << 8) // copy-on-write |
修改 kalloc.c 如下:
int ref[PHYSTOP/PGSIZE+1];// 物理页面的引用计数 | |
//... | |
void | |
freerange(void *pa_start, void *pa_end)// 分配时初始化 | |
{ | |
char *p; | |
p = (char*)PGROUNDUP((uint64)pa_start); | |
for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE){ | |
ref[(uint64)p/PGSIZE]=1;// 初始的引用计数为 1 | |
kfree(p); | |
} | |
} | |
// Free the page of physical memory pointed at by pa, | |
// which normally should have been returned by a | |
// call to kalloc(). (The exception is when | |
// initializing the allocator; see kinit above.) | |
void | |
kfree(void *pa) | |
{ | |
if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP) | |
panic("kfree"); | |
acquire(&kmem.lock); | |
int remain=--ref[(uint64)pa/PGSIZE];// 引用计数 - 1 | |
release(&kmem.lock); | |
if(remain>0){// 仍然有进程在使用该物理页,不释放 | |
return; | |
} | |
// 释放页面 | |
struct run *r; | |
// Fill with junk to catch dangling refs. | |
memset(pa, 1, PGSIZE); | |
r = (struct run*)pa; | |
acquire(&kmem.lock); | |
r->next = kmem.freelist; | |
kmem.freelist = r; | |
release(&kmem.lock); | |
} | |
// Allocate one 4096-byte page of physical memory. | |
// Returns a pointer that the kernel can use. | |
// Returns 0 if the memory cannot be allocated. | |
void * | |
kalloc(void)// 分配页面 | |
{ | |
struct run *r; | |
acquire(&kmem.lock); | |
r = kmem.freelist; | |
if(r){ | |
kmem.freelist = r->next; | |
if(ref[(uint64)r/PGSIZE]!=0) panic("kalloc: ref count");// 未分配时引用计数必为 0 | |
ref[(uint64)r/PGSIZE]=1;// 初始计数为 1 | |
} | |
release(&kmem.lock); | |
if(r){ | |
memset((char*)r, 5, PGSIZE); // fill with junk | |
} | |
return (void*)r; | |
} | |
void ref_increase(uint64 pa){// 使引用计数 + 1 的函数 | |
acquire(&kmem.lock); | |
if(pa%PGSIZE!=0||pa>=PHYSTOP||ref[pa/PGSIZE]<1) panic("ref increase err"); | |
ref[pa/PGSIZE]++; | |
release(&kmem.lock); | |
} |
在 trap.c 中修改如下:
// handle an interrupt, exception, or system call from user space. | |
// called from trampoline.S | |
// | |
void | |
usertrap(void) | |
{ | |
// ... | |
if(r_scause() == 8){ | |
// system call | |
// ... | |
} else if(r_scause() == 15||r_scause()==13){//13 和 15 都是 page fault | |
//page fault | |
uint64 va=r_stval(); | |
if(!cow_pagefault(p->pagetable,va)){//copy-on-write 处理失败 | |
setkilled(p);// 杀死进程 | |
} | |
} else if((which_dev = devintr()) != 0){ | |
// ok | |
} else { | |
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid); | |
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval()); | |
setkilled(p); | |
} | |
// ... | |
} |
vm.c 修改如下:
int | |
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)// 映射到同一个物理页面 | |
{ | |
pte_t *pte; | |
uint64 pa, i; | |
uint flags; | |
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); | |
flags = PTE_FLAGS(*pte);// 获取当前页面标志位 | |
if(flags&PTE_W){// 有写标志,说明不是 copy-on-write 页 | |
flags &= ~PTE_W;// 取消写标志 | |
flags |= PTE_C;//copy-on-write 标志 | |
} | |
flags |= PTE_R;// 设置可读 | |
*pte = PA2PTE(pa)|flags;//pte 标志设置为 flags | |
mappages(new, i, PGSIZE, pa, flags);// 将新页映射到同一个物理地址 pa | |
ref_increase(pa);// 引用计数 + 1 | |
} | |
return 0; | |
} | |
// Copy from kernel to user. | |
// Copy len bytes from src to virtual address dstva in a given page table. | |
// Return 0 on success, -1 on error. | |
int | |
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) | |
{ | |
uint64 n, va0, pa0; | |
pte_t *pte; | |
while(len > 0){ | |
va0 = PGROUNDDOWN(dstva); | |
if(va0 >= MAXVA)// 越界 | |
return -1; | |
pte = walk(pagetable, va0, 0); | |
if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)// 无效页或者用户不能访问的页 | |
return -1; | |
if((*pte & PTE_W)==0){// 无写标志,可能是 copy-on-write 页导致的只读 | |
if(!cow_pagefault(pagetable,va0)) return -1; | |
} | |
// 有写标志,直接拷贝 | |
pa0 = PTE2PA(*pte); | |
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; | |
} | |
int cow_pagefault(pagetable_t pagetable,uint64 va){// 处理对 copy-on-write 页的写操作 | |
if(va>=MAXVA) return 0;// 越界 | |
pte_t *pte; | |
if(!(pte=walk(pagetable,va,0))){// 无效 va | |
return 0; | |
} | |
if(!(*pte&PTE_V)||!(*pte&PTE_C)){// 无效页或者不属于 copy-on-write 页 | |
return 0; | |
} | |
va = PGROUNDDOWN(va); | |
char*mem=kalloc();// 分配一个物理页 | |
if(!mem){ | |
return 0; | |
} | |
uint64 pa = PTE2PA(*pte); | |
memmove(mem, (char*)pa, PGSIZE);// 复制到新页 | |
uint flags = PTE_FLAGS(*pte); | |
*pte = PA2PTE((uint64)mem) | flags;// 复制旧页的标志位到新页 | |
*pte |= PTE_W;// 新页可写 | |
*pte &= ~PTE_C;// 新页取消 copy-on-write 标志 | |
kfree((void*)pa);// 源页引用计数 - 1 | |
return 1; | |
} |
# Test
一开始没有注意到 scause=13 也是 page fault,导致 usertest 部分一直卡住无输出,查看日志才知道有大量 unexcepted scause。