6s081 lab copy on write record
This experiment requires the implementation of copy on write. Let's consider what we need to do first
- Modify the fork to directly copy the contents of the page table of the parent process to the page table of the child process without copying the memory of the parent process when generating the child process. At the same time, set the page table item to be non writable and as the COW page table (define PTE_C in riscv.h macro);
- pagefault is generated when the child process or parent process writes to memory. At this time, check whether the pte item has PTE_C mark, if there is a mark, allocate memory, copy the COW page table memory to the newly allocated memory, replace the page table entry, modify the physical memory location and mark bit to writable non COW pages;
- Modify copyout, the content is similar to 2
- Considering when to release memory pages, we need an array to record the number of times each page is referenced_ Ref, when the reference is 1, if it is kfree, the release operation will be executed; otherwise, only page will be executed when kfree_ ref–; At the same time, kalloc must be the first time that memory is allocated, page_ref is directly set to 1.
Then the practice is very clear.
Modify riscv h
Look at this picture (I stole it)
Add macro definition:
#define PTE_C (1L << 9)
Modify kalloc c
To modify 4 parts
- Add global variable page_ref records the number of references per page
int page_ref[PHYSTOP/PGSIZE];
- Initialize page in kinit()_ Ref is all 1 to prepare for kfree in the next step. In fact, the memory available to users here is not as much as PHYSTOP/PGSIZE, but the memory of the kernel does not seem to be free, so it does not affect it
void kinit() { for(int i = 0 ; i < PHYSTOP/PGSIZE; ++i) page_ref[i] = 1; initlock(&kmem.lock, "kmem"); freerange(end, (void*)PHYSTOP); }
- Modify kfree and maintain page_ref [], hide page with kfree and kalloc_ Ref [] can make most of the code do not need to be modified. This variable can be said to be "nonexistent"
void kfree(void *pa) { if( --page_ref[(uint64)pa/PGSIZE] > 0 )//Just add this judgment 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); }
- When modifying kalloc(), set the number of references to 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; //Add this sentence } release(&kmem.lock); if(r) memset((char*)r, 5, PGSIZE); // fill with junk return (void*)r; }
Modify the fork logic in VM C uvmcopy c
Do not copy memory, only copy page tables
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); //Change to not writable *pte = *pte | PTE_C; //Mark as cow page 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];//Maintain references } return 0; err: uvmunmap(new, 0, i / PGSIZE, 1); //There is no need to move here. Release or call the kfree. The kfree we modify will be changed //Maintenance page_ref instead of direct free memory return -1; }
Modify usertrap()
In case of write error (15), judge whether PTE has PTE_C mark. If yes, copy a page, modify the mark and remap
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); } }
Modify copyout()
The logic of this part is consistent with the user trap.
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; } //Start from here 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; } //Add it here 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; }
Last reminder, in defs Add the prototype of walk function in H