Nachos Project #4: Due Friday 29 May 1998
Demand Paged Virtual Memory
Copyright information
Last modified: Fri May 15 13:19:30 CDT
Project #4 is due on Friday, 29 May, at 7:30 AM. Use the
submit-project Project_4 command to hand in your work as you
did in previous project steps. As before, submit partial results, but
make each submission self-contained and complete in terms of
demonstration and explanation.
Basic goals
In this project, you implement virtual memory with demand paging.
General requirements
The good old general requirements still apply.
Special requirements and comments for this assignment
- There is no new source code from the initial Nachos
implementation in this project. But, you need to compile Nachos with
the DVM and DUSE_TLB options. code/vm/Makefile
is set up to compile Nachos appropriately for the use of virtual
memory and the TLB.
- Keep in mind that, using the TLB, a "PageFault"
exception is really just a miss on the TLB. You must write code to
determine whether it is just a TLB miss, or a true page fault,
and take appropriate action in each case.
- In Project #4, the later tasks often redo parts of the earlier
tasks. I will evaluate the cumulative results of the tasks, so you do
not need to save and explain intermediate states. For example, you do
not need to present a version of Nachos for task 1 that uses the TLB
with normal page tables, given that you have changed over to inverted
page tables in task 2. So, the code that you write to translate
addresses through the normal page tables will disappear. I recommend
strongly, though, that you do the tasks in the order given, and do not
try to skip intermediate code that gets replaced later. I adapted the
sequence of work in the instructions from Prof. Anderson's course at
Berkeley, and I believe that it is very well designed to lead you
efficiently to the final result. Any time that you save by skipping
steps you will probably pay back many times over in debugging too much
new code at once. Also, if you can't finish the whole project, I will
give much more partial credit for a clear and well-tested
implementation of the first tasks than for a failed attempt to do the
whole thing in one chunk.
- The basic structure by which virtual memory moves pages around
and keeps track of them is much more important than the cleverness of
the strategy for choosing which pages to have in memory and in the
TLB. The page-replacement strategies have a crucial effect on
speed performance, but they have little or no impact on the overall
structure of OS code. So, given well structured code for replacing a
page arbitrarily, it is easy to slide in cleverer algorithms later.
The project tasks
- Modify your Nachos OS to use the TLB instead of hardware
interpretation of page tables. For a start, you should leave the page
tables as they are, but add code to the PageFault exception in
ExceptionHandler to do translation through the page table
whenever there is a TLB miss. You must also add code to maintain the
contents of the TLB. On each context switch, you need to clear the TLB
by marking all entries invalid. When there is a PageFault
exception, you need to add an entry to the TLB containing the missing
page (which, for now, is guaranteed to be in main memory), throwing
out an old entry if the TLB was already full, and re-executing the
instruction causing the fault (do this by returning from
ExceptionHandler without incrementing the PC). It is OK to
throw out a randomly chosen TLB entry. You may implement a cleverer
replacement strategy if you like, but I will give extra credit only if
you present a sensible reason to hope that it really improves
things.
- Replace the normal page tables with a single inverted page table,
mapping physical page frames in main memory to virtual page
numbers. Now, address translation must search for the appropriate
physical page frame. Make the search fast and simple with a trivial
hashing function: map the virtual page number p to the page
frame numbered p mod s, where s is the size of main
memory measured in page frames. Use chaining or open addressing to
search when more than one virtual page number gets mapped to the same
page frame. Notice that the inverted page table needs to keep track of
the address space owning a given frame, as well as the virtual page
number in that address space. You might as well incorporate the
"dirty" and "use" bits as well, although you won't need them until the
next task. You may decide to add other information to this table as
your work progresses. Notice that some information in the inverted
page table repeats information in the TLB. You must keep the two
consistent at appropriate times.
Now, implement virtual memory with demand paging. For each
address space, create a swap file containing the entire contents of
the address space. Copy in the executable file, and initialize all
other contents to 0. Whenever a page is addressed that is not in main
memory, load it into an appropriate page frame. Notice that virtual
addresses are the same as physical addresses in the swap file. If main
memory is full, remove any page frame that you like, and write it to
the appropriate swap file. I will give a modest amount of extra
credit, for a good immplementation of the clock algorithm or some
other sensible method for choosing a page to swap out. Please don't
spend time on replacement algorithms until you've solved all of the
basic problems really well.
Even though the current implementation of the file system is
synchronous, you should assume that while a given thread is paging,
many other threads can run. Make sure that there are no problems
resulting from many interleaved accesses to virtual memory. Create
test cases that yield the CPU just before and after access to
files. There are three main problems to watch out for:
- A page frame could disappear before it can be accessed. It may be
sufficient just to make this highly unlikely, rather than
impossible.
- Worse, a page frame could be reallocated before the read from disk
completes, leading to an inconsistency between the memory map and the
actual contents of memory.
- A page might be fetched from disk just before it gets updated by
writeback from main memory. This is the cache coherence
problem, applied to main memory as a disk cache.
You need some careful application of synchronization tools to avoid
these sorts of problem.
- (Com Sci 330 only).
Implement sparse virtual address spaces. Associate with each space
a segment table mapping virtual addresses to addresses in the swap
file. Allocate space in the swap file only for pages that are actually
written. This means that the code for the PageFault exception
must allocate swap space when necessary, and update the swap-segment
table. Take sensible steps to avoid unnecessary file operations, such
as reading and writing uninitialized pages full of 0s. If you do this
right, there is no need to consult the segment table when a page is
actually in main memory.
Now, avoid moving the executable file for a user thread by mapping
it onto a segment of virtual memory. You now need to keep track in the
segment table of which file is associated with each segment.
Don't do this, but take a look
Here's what the Com Sci 330 assignment is aiming towards. You don't
need to do it, but I couldn't bear to throw it away. Mapping the
executable file exercises most of the structure of a general
segment-to-file map, but cuts the total coding a bit. Think about even
more flexible manipulations of virtual memory. One intriguing idea is
to build the whole file system on top of virtual memory, rather than
using files to implement VM. That is, the VM code works directly with
the DISK to implement segementation and demand paging. Then, a file is
defined to be a certain segment of virtual pages.
Now, map arbitrary files, in addition to the swap file, to virtual
memory. Provide a new system call, MemMap, to map a given
file to a given range of virtual pages. You can use the same segment
table above, but you now need to keep track of which file is
associated with each segment. It is the user's responsibility to
choose virtual addresses that are not already associated with another
file, including the swap file. You must add code to the file
Close operation to flush page frames in main memory to the
file, and eliminate the allocated segment from the table. Use your new
utility to avoid the initial loading of the executable into an address
space: merely map the executable file onto the virtual memory. In
this project, you should leave all of the kernel's private data,
including page tables and segment tables, in the special kernel
address space that is not contained in simulated MIPS memory.
But, comment briefly on the proper disposition of kernel data if we
implemented Nachos entirely on the MIPS. Which parts of
kernel data should be paged, and which need to be locked into main
memory? Is the locking required for logical correctness, or for good
performance?
Good advice
See the Nachos Road Map
section on experience with virtual memory for some useful
ideas. Watch out, though: the assignment used at Duke is quite
different from ours.
- Numbers 1 and 2 apply directly to our work.
- The information in the "core map" described in 3 is essentially
the same as that in the inverted page table that we are using. The
main difference between the two phrases "core map" and "inverted page
table" appears to be that the former is intended to augment a normal
page table, while the latter is intended to replace it.
- Number 4 is real good advice. Because we are already using the
software-loaded TLB, though, we have more flexibility in dealing with
the problems of concurrency than Narten has with the
hardware-interpreted page tables. Some problems that he views as
requiring mutual exclusion may be solvable by careful descriptions of
the various states that a page can be in, using extra bits in the
inverted page map. For example, it might be useful to distinguish a
page that is in the process of being written out or read in from the
swap file.
- Number 5 does not apply to us. The "shadow" page table is replaced
by the inverted page table. Prof. Narten calls this table a "shadow",
because he chose to implement virtual memory through
hardware-controlled page tables before switching to a TLB, so he must
maintain each address space's page table as well as the particular
table used by the CPU at a given moment. I like the Berkeley ordering
of switching to TLB first and then working on virtual memory. In this
order, you are not constrained by the hardware's use of page tables.
- Number 6 is too complicated for us. Narten asks his students to
implement delayed loading of the executable first. I prefer to let
that be a simple application of memory-mapped files, so that it never
requires special conditional code. He also asks for an immediate
avoidance of unnecessary swapping of uninitialized memory. You can use
his "scenario" 1 in all cases. I prefer to avoid the waste inherent in
this strategy as part of the implementation of sparse virtual memory
with allocation on demand, in the 330/optional part of the project.
- I am puzzled and a bit worried by number 7. In principle, it seems
OK to let the kernel create page faults, as long as it is careful not
to get into an infinite recursion. But, Narten appears to be advising
us that it is not safe to take a PageFault exception through
ReadMem or WriteMem within kernel code. I found that
RaiseException, which is part of the machine implementation
in code/machine/machine.cc, seems to assume that it can only
happen in user mode. In particular, it sets privileges to
UserMode at the end, and doesn't take into account the
possibility that the exception occurred in SystemMode. I
don't know whether this is real MIPS behavior, or a
peculiarity of the simulation. We will have to figure this out as we
go, but certainly be prepared for the need to lock pages into memory
(not to be confused with locking some data for mutual exclusion).