Project 3: Virtual memory management

Due date: May 28th

Questions: grover@cs.ucsb.edu

Overview

The goal of this project is to implement demand-paged virtual memory management on top of the basic multiprogramming kernel developed in project 2. In project 2, all the pages for a process were loaded into physical memory. This had two implications:

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 (pagetables) 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 Page Fault Exception 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:

Nachos already contains code to do virtual to physical translation and to raise a PageFaultException. For this project, you will:

Suggested project stages:

For this project, you will primarily be working with files in `userprog' and `machine' directories. We suggest the following stages. We urge you to keep an eye at all times on the data structures you have chosen to store and access information. The hope is that after each stage, your design shall become more concrete and perhaps you shall have a better perspective of what you need to do and how you would like to do it.

Stage 1:
Make sure that you understand the TranslationEntry class as given in "machine/translate.h". Ask yourself why each component of a pagetable 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.
  • If there is no unused frame in memory, scan the physical memory for selecting a victim page. Use the Enhanced Second Chance Algorithm which is described in section 9.5.4.3 (pp. 311-312) 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.
  • Use a single file called SWAP with 256 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).

    Recommended Data Structures

    The page fault handler requires some auxiliary data structures to accomplish its task. The following data structures may be useful.
  • A boolean flag per 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.
  • Other designs are possible; the above data structures are just to give you an idea of how it can be done.

    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 simultaneosly. 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 and the next, you will extend the page fault handler you developed in stage 6 to allow users to specify priorities for pages. A page can be specified to be high-priority or low-priority. Within each process, no high-priority page is to be paged out while a low-priority page is still in memory. Note that these priorities are within the address-space of each process and are not global. In this stage, we want you to add a system call to Nachos. By now, you should have a good idea of how system calls work. Basically you would need to add code for a new system call in test/start.c and in userprog/syscall.h. The new system call should be called MemPri() and its signature should be as follows:

    #define PRI_LO      0
    #define PRI_HIGH  1

    int MemPri (unsigned int baseAddress, unsigned int numPages, int  newPriority);

    #define ERR_ILLEGAL_ADDRESS  -1
    #define ERR_ ILLEGAL_PRIORITY -2
    #define MEMPRI_SUCCESSFUL       0

    Write a dummy implementation for this system call which just prints out that it was invoked by user code and returns MEMPRI_SUCCESSFUL. Make sure to write a simple program in the test directory which uses this system call and make sure that your addition of this system call to Nachos does indeed work.

    Stage 8

    The idea is that using this new system call, programmers should be able to give Nachos a hint that certain pages in the virtual address space of a process should have a higher priority (and therefore should be less probable to be selected as victims during the page replacement process). This feature would be used by programmers to tag certain pages (for instance those containing heavily used data) so that they are less likely to be swapped out of memory.

    Here is how this is to work. Each virtual page of any process should have an associated priority which could be PRI_HIGH or PRI_LO. These values should be defined as global constants in syscall.h (in userprog directory so that user processes as well as kernel code can use these constants). When the user invokes this system call, the kernel remembers that numPages number of pages starting at virtual address baseAddress  should have the priority set to newPriority. Unless explicitly specified by a MemPri() call, the priority of a page is PRI_LO.

    Remember to check for invalid arguments to MemPri(). If any of the arguments are invalid, the appropriate error code should be returned (the possible return values are defined above and should easy to interpret :)). Note that this system call is just a hint to the Nachos virtual memory manager. If it fails due to invalid arguments, the process should not be halted.

    The new page-replacement policy works in two phases.

    Phase 1
    select a page frame for replacement using your page replacement policy from stage 7
    Let us call that page `p'.
    Phase 2
    if (priority of `p' is PRI_LO)
    then the search ends and `p' is replaced
    else
    let `R' be the process to which `p' belongs.
    while (q ranges over all pages of `R'  which are in memory)
        if `q` is a potential victim, then end the search and replace `q`
    This algorithm uses the predicate `potential victim'  . The interpretation of that is something which you need to define concretely. We suggest you might like to use pages with priority PRI_LO which don't have the use bit or the dirty bit set.
     

    What to Turnin

    Required Output