100 lines
3.5 KiB
HTML
100 lines
3.5 KiB
HTML
|
<h2>Programming Assignment: Copy-on-Write Fork for xv6</h2>
|
||
|
|
||
|
<p>
|
||
|
Your task is implement copy-on-write fork in the xv6 kernel. You are
|
||
|
done if your modified kernel executes both the cow and usertests
|
||
|
programs successfully.
|
||
|
|
||
|
<h3>The problem</h3>
|
||
|
|
||
|
The fork() system call in xv6 copies all of the parent process's
|
||
|
user-space memory into the child. If the parent is large, copying can
|
||
|
take a long time. In addition, the copies often waste memory; in many
|
||
|
cases neither the parent nor the child modifies a page, so that in
|
||
|
principle they could share the same physical memory. The inefficiency
|
||
|
is particularly clear if the child calls exec(), since then most of
|
||
|
the copied pages are thrown away without ever being used. Of course,
|
||
|
sometimes both child and parent modify memory at the same virtual
|
||
|
address after a fork(), so for some pages the copying is truly needed.
|
||
|
|
||
|
<h3>The solution</h3>
|
||
|
|
||
|
The goal of copy-on-write (COW) fork() is to defer allocating and
|
||
|
copying physical memory pages for the child until they are actually
|
||
|
needed, in the hope that they may never be needed.
|
||
|
|
||
|
<p>
|
||
|
COW fork() creates just a pagetable for the child, with PTEs for user
|
||
|
memory pointing to the parent's physical pages. COW fork() marks all
|
||
|
the user PTEs in both parent and child as read-only. When either
|
||
|
process tries to write one of these COW pages, the CPU will force a
|
||
|
page fault. The kernel page-fault handler detects this case, allocates
|
||
|
a page of physical memory for the faulting process, copies the
|
||
|
original page into the new page, and modifies the relevant PTE in the
|
||
|
faulting process to refer to the new page, this time with the PTE
|
||
|
marked writeable. When the page fault handler returns, the user
|
||
|
process will be able to write its copy of the page.
|
||
|
|
||
|
<p>
|
||
|
COW fork() makes freeing of the physical pages that implement user
|
||
|
memory a little trickier. A given physical page may be referred to by
|
||
|
multiple processes' page tables, and should be freed when the last
|
||
|
reference disappears.
|
||
|
|
||
|
<h3>The cow test program</h3>
|
||
|
|
||
|
To help you test your implementation, we've provided an xv6 program
|
||
|
called cow (source in user/cow.c). cow runs various tests, but
|
||
|
even the first will fail on unmodified xv6. Thus, initially, you
|
||
|
will see:
|
||
|
|
||
|
<pre>
|
||
|
$ cow
|
||
|
simple: fork() failed
|
||
|
$
|
||
|
</pre>
|
||
|
|
||
|
The "simple" test allocates more than half of available physical
|
||
|
memory, and then fork()s. The fork fails because there is not enough
|
||
|
free physical memory to give the child a complete copy of the parent.
|
||
|
|
||
|
<p>
|
||
|
When you are done, your kernel should be able to run both cow and
|
||
|
usertests. That is:
|
||
|
|
||
|
<pre>
|
||
|
$ cow
|
||
|
simple: ok
|
||
|
simple: ok
|
||
|
three: zombie!
|
||
|
ok
|
||
|
three: zombie!
|
||
|
ok
|
||
|
three: zombie!
|
||
|
ok
|
||
|
file: ok
|
||
|
ALL COW TESTS PASSED
|
||
|
$ usertests
|
||
|
...
|
||
|
ALL TESTS PASSED
|
||
|
$
|
||
|
</pre>
|
||
|
|
||
|
<h3>Hints</h3>
|
||
|
|
||
|
Here's one reasonable plan of attack. Modify uvmcopy() to map the
|
||
|
parent's physical pages into the child, instead of allocating new
|
||
|
pages, and clear PTE_W in the PTEs of both child and parent.
|
||
|
Modify usertrap() to recognize a page fault. When a page fault occurs
|
||
|
on a COW page, allocate a new page with kalloc(), copy the old page to
|
||
|
the new page, and install the new page in the PTE with PTE_W set.
|
||
|
Next, ensure that each physical page is freed when the last PTE
|
||
|
reference to it goes away (but not before!), perhaps by implementing
|
||
|
reference counts in kalloc.c. Finally, modify copyout() to use the
|
||
|
same scheme as page faults when it encounters a COW page.
|
||
|
|
||
|
<p>
|
||
|
It may be useful to have a way to record, for each PTE, whether it is
|
||
|
a COW mapping. You can use the RSW (reserved for software) bits in
|
||
|
the RISC-V PTE for this.
|