Lab: Copy-on-Write Fork for xv6

Implement Copy-on-Write (hard)

We need to implement copy-on-write optimization in fork calls. Let's follow the hints: When user calls fork(), a new process will create a new page table (uvmcopy()), which contains the same ptes as the parent. And we clear the write flag PTE_W for every valid page in both tables.

Is this enough? Let's move on and leave it okay temporarily.

Copy Page Table on Fork

So next, a store instruction in user code causes a store page fault scause = 15, we go into the usertrap() routine, a general exception handler. We then retrieve the faulting virtual address from stval register and walk through the page table to find the cause. Suppose we perfectly find it, now shall we allocate a new page and restore the writing bit? Wait! If this page was read-only before copying, now we have a writable page in one of the processes, that's horrible!

Now the question comes, how can you decide the page was read-only or r/w? So we need to do something in the uvmcopy() so that it is easy to distinguish the type. My solution is using a bit in the RSW area:

64-bit page table entry in risc-v

64-bit page table entry in risc-v

#define PTE_COW (1L << 8)    // 1: cow page, 0: read-only page

When copying the page table, we only set the cow bit if this page was writable for both tables. Now we are trapped in the page fault handler, we can allocate pages only for the cow ones and leave the read-only ones just there. Don't forget reset the flag bits:

*pte |= PTE_W;
*pte &= ~PTE_COW; // not cow any more, avoid causing another copy

The faulting process restarts the store instruction, and this time it is writable.

Add Reference Count for Physical Pages

Now the user wants to terminate its process, and kernel calls uvmfree() in kernel/vm.c file. The do_free option is set in the uvmunmap() function, and we free all physical pages belonging to the process.

What about the read-only pages that have multiple mappings? If one process frees it, any other process will have no access to this physical page. That's why we need to record the number of references to every physical page allocated by kalloc(). (kernel data and text are excluded) We only free the pages having no references.

How can we maintain this data structure? In my solution, just a uint array that have the same number entries as physical pages.