Questions: david@cs.ucsb.edu
Demand-paged virtual memory removes both these restrictions. It allows a process to execute with only a fraction of its address-space resident in physical memory. It also allows the total memory footprint of active processes to be larger than the physical memory. It achieves this by:
Virtual memory works in the following way. All addresses generated by a user process are virtual addresses. Address translation hardware checks every such address generated by a user process (read/write, instruction/data) as it tries to map it to the corresponding physical address. If the page that contains the virtual address referenced by the user process is currently resident in physical memory, the translation proceeds as it used to without virtual memory. If, on the other hand, the page is not in physical memory, a Page Fault Exception is generated. This causes the processor to leave the user mode and switch to kernel mode. The kernel responds a PageFaultException by running a routine (called the Page fault Handler) whose purpose is to bring the referenced page into physical memory (for this, it may have to move some other page to the disk-based backing store). In general, the Page Fault Handler needs to perform the following operations:
Stage 1:
Make sure that you understand the
TranslationEntry class as given in
"machine/translate.h". Ask yourself why each component
of a page-table has been included. See the method
Machine::Translate in "machine/translate.cc" to
understand how these fields are used. All the flags in this structure
have been described in class.
Stage 2:
Trace how the MIPS simulator (in
"machine/mipssim.cc") executes instructions. As a part
of executing an instruction, the machine accesses main memory for
loading the instruction itself ([code segment]) and possibly
for its operands ([data/stack segment]). Figure out how memory
access is implemented and where the virtual to physical translation is
performed. It is during the translation process that the machine can
determine if the virtual address it is trying to access belongs to a
page which does not reside in physical memory. Figure out how, when
and where the PageFaultException is thrown.
Stage 3:
In stage 2, you would have figured out how,
when and where the PageFaultException is raised. This Exception
results in control being passed back to the kernel and is supposed to
be handled in a manner similar to system calls. Currently, this
exception is not handled. Add code to exception.cc to
call a stub routine when this Exception is raised. This will be your
page fault handler. As a part of raising an Exception, the processor
saves some information that will be used by the kernel to handle the
Exception. Figure out what information is set up and in which
registers. Note that, at this point, this mechanism is not exercised
by program execution as processes are loaded in their entirety and no
page faults are generated.
Stage 4:
Figure out how to start a process with
none of its pages in memory. For this, you will need to change the
code that you wrote in Project 2 for process creation. You may also
need to modify the pagetable structure (in the TranslationEntry
class) to keep track of pages that are not in memory. Note that you
are free to add fields to the this structure (as long as you don't
disturb the existing fields). You will need to keep track of the
location from which disk-resident pages are to be loaded. Remember
that, initially the pages of a process are all in the executable
file. Once a page has been brought in to memory, any subsequent flush
of this page to disk (during page replacement) should be to backing
store (and not to the executable file). You will also need to allocate
space in the backing store for the pages of this process. You can
choose to be conservative and allocate space for the entire virtual
address space of the process on the backing store at creation time.
You can be even more conservative and choose to copy the entire
executable file into the allocated space at startup. If you did this,
you would need to only concern yourself with moving pages between
backing store and the memory during page fault handling. This is only
a suggestion. Alternate implementations are more than welcome.
Stage 5
At this point, you are all set to receive a page
fault. We suggest that you make your dummy page fault handler (set up
in stage 3) simply print some debugging information and return. Using
this scheme, you should make sure that control actually flows to your
page fault handler during program execution. You don't service the
page fault at this stage. Therefore, you should run into an infinite
loop where the machine keeps raising page faults.
Stage 6
Now you are all set to implement a page
replacement algorithm.
Page Fault Handling
The page fault handler gets control as a result of the machine raising a page fault exception. The handling of this exception involves the following tasks.Use a single file called SWAP with 512 sectors to implement the backing store. The size of the swap sectors is the same as that of a physical page frame. You should use the stub implementation of the file system already provided with Nachos (look into filesys/filesys.h and filesys/openfile.h).If there is no unused frame in memory, scan the physical memory for selecting a victim page. Use the Second Chance Algorithm which is described in section 9.5.4.3 (pp. 310-311) in the text-book [Operating System Concepts by Silberschatz and Galvin, fifth edition]. If necessary, allocate space on the backing store to receive the contents of the victim page (assuming that it is dirty and needs flushing). Initiate I/O to write the contents of the victim page to the backing store. Adjust the pagetable for the process to which the victim page belongs to reflect the fact that it is no longer resident in memory. Locate the page for which the fault was generated on the backing store; initiate I/O to load the page into the page frame selected in the previous steps. Adjust the pagetable for the faulting process to reflect the fact that the desired page is now resident in memory. Return to user mode and restart the instruction that caused the fault. Restarting the instruction might involve readjusting the instruction pointer so that the machine tries to re-execute the instruction for which the page-fault was generated in the first place.
Recommended Data Structures
The page fault handler requires some auxiliary data structures to accomplish its task. The following data structures may be useful.Other designs are possible; the above data structures are just to give you an idea of how it can be done.A boolean flag per virtual page indicating whether a given virtual page of a process is resident in physical memory or on backing store. The mapping between a virtual page of a process and its location (whether in physical memory or on backing store). A map of the backing store to keep track of space allocation/de-allocation. Since the page replacement policy would need to scan the physical memory frames looking for potential victims, you would need a table that has one entry for each physical memory frame. The entry could contain information about the process owning the page and possibly the corresponding page of process virtual memory which maps to that physical page. A table of the address space information of all active processes in the system possibly indexed by the process_ids.
Once you get this working, you should be able to execute programs normally. Use the "shell" program in the test directory to launch multiple programs in Nachos simultaneously. You should test your code under various conditions of system load, including one process with an address space larger than physical memory, and several concurrently running processes with combined address spaces larger than physical memory. The sort program in the test directory is an example of a program designed to stress the virtual memory system.
Stage 7 In this stage, you will extend your memory structures and page fault handler to support copy-on-write. This means that when you copy a page (as in a fork system call), that you do not actually copy the page. Instead, you:
Stage 8 In this stage, you will implement demand zero-filled pages. This is a technique where pages are not allocated, in either main memory or in backing store, until they accessed. This is useful for pages of uninitialized data, such as the heap or stack.
When the page is accessed for the first time, the page fault is handled by allocating a new page, which is then cleared (filled with zeros). The faulting page table entry is then updated to point to the new physical page. Up until now, a page was always either in memory or in the backing store. To do this, you will need to updated your translationEntry to handle this new page state.
You are to: