6s081 lab copy on write 记录
本实验要求实现copy on write,先来考虑我们需要干些什么
- 修改fork,在生成子进程时不拷贝父进程内存,而是直接将复制父进程的页表内容到子进程的页表,同时把页表项设置为不可写入且为COW页表(在riscv.h中宏定义PTE_C);
- 在子进程或者父进程写入内存时产生pagefault,此时检查pte项是否有PTE_C标记,若有标记则分配内存,将COW页表内存拷贝到新分配的内存,并替换页表项,修改物理内存位置以及标记位为可写非COW页;
- 修改copyout,内容和2相似
- 考虑何时释放内存页,我们需要一个数组,记录每个页被引用次数page_ref,引用为1时若被kfree则执行释放操作,否则kfree时只page_ref–;同时kalloc时必定是内存第一次被分配,page_ref直接设为1。
那么做法就很明确了。
修改riscv.h
看这张图(图我盗的)
添加宏定义:
#define PTE_C (1L << 9)
修改kalloc.c
要修改4个部分
- 添加全局变量page_ref记录每页的引用次数
int page_ref[PHYSTOP/PGSIZE];
- 在kinit()里初始化page_ref为全1,为下一步kfree做准备,实际上这里可以被用户使用的内存没PHYSTOP/PGSIZE这么多,不过kernel的内存好像不会被free所以并不影响
void
kinit()
{
for(int i = 0 ; i < PHYSTOP/PGSIZE; ++i)
page_ref[i] = 1;
initlock(&kmem.lock, "kmem");
freerange(end, (void*)PHYSTOP);
}
- 修改kfree,维护page_ref[],用kfree和kalloc隐藏page_ref[]可以使大部分代码不需要修改,这个变量可以说“不存在”
void
kfree(void *pa)
{
if( --page_ref[(uint64)pa/PGSIZE] > 0 )//加这句判断就行
return;
struct run *r;
if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
panic("kfree");
// 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);
}
- 修改kalloc(),分配时要把引用次数置为1
void *
kalloc(void)
{
struct run *r;
acquire(&kmem.lock);
r = kmem.freelist;
if(r){
kmem.freelist = r->next;
page_ref[(uint64)r/PGSIZE] = 1; //加这句
}
release(&kmem.lock);
if(r)
memset((char*)r, 5, PGSIZE); // fill with junk
return (void*)r;
}
修改fork逻辑,在vm.c的uvmcopy.c
不复制内存,只复制页表
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");
*pte = *pte & (~PTE_W); //修改为不可写入
*pte = *pte | PTE_C; //标记为cow页
pa = PTE2PA(*pte);
flags = PTE_FLAGS(*pte);
// if((mem = kalloc()) == 0)
// goto err;
// memmove(mem, (char*)pa, PGSIZE);
if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
goto err;
}
++ page_ref[(uint64)pa/PGSIZE];//维护引用次数
}
return 0;
err:
uvmunmap(new, 0, i / PGSIZE, 1);
//这里不需要动,释放还是调用的kfree,我们修改的kfree会
//维护page_ref而不是直接free内存
return -1;
}
修改usertrap()
遇到写入错误(15),判断pte是否有PTE_C标记,若有复制一页,修改标记,重新map
else if( r_scause() == 15 ){
uint64 va = r_stval();
pte_t *pte = walk( p->pagetable, va, 0);
if(*pte & PTE_C){
char* mem;
uint64 flags, pa;
pa = PTE2PA(*pte);
if((mem = kalloc()) == 0){
// printf("no mem remain on COW\n");
p->killed = 1;
exit(-1);
}
memmove(mem, (char*)pa, PGSIZE);
flags = (PTE_FLAGS(*pte) | PTE_W) & (~PTE_C);
uvmunmap(p->pagetable, PGROUNDDOWN(va), 1, 1);
if(mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, (uint64)mem, flags) != 0){
kfree(mem);
p->killed = 1;
exit(-1);
}
}else{
p->killed = 1;
exit(-1);
}
}
修改copyout()
此部分逻辑个usertrap一致。
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
uint64 n, va0, pa0, flags;
pte_t *pte;
char* mem;
while(len > 0){
va0 = PGROUNDDOWN(dstva);
pa0 = walkaddr(pagetable, va0);
if(pa0 == 0){
return -1;
}
//从这开始加
pte = walk(pagetable, va0, 0);
if(*pte & PTE_C){
if((mem = kalloc())==0){
return -1;
}
memmove(mem, (char*)pa0, PGSIZE);
flags = ( PTE_FLAGS(*pte) | PTE_W ) & (~PTE_C);
uvmunmap(pagetable, va0, 1, 1);
if(mappages(pagetable, va0, PGSIZE, (uint64)mem, flags) != 0){
kfree(mem);
return -1;
}
pa0 = (uint64)mem;
}
//加到这
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;
}
最后提醒,在defs.h里添加walk函数的原型