CS 134 Homework Assignment #4: Virtual Memory

Wiki Answers Due: 04-03-2014
Design Documents Due: 04-03-2014
Patch 1 Due: 04-10-2014
Patch 2 Due: 04-17-2014
Final Patches Due: 05-01-2014
Code Reviews Due: 05-07-2014

Introduction

You have improved OS/161 to the point that you can now run user processes. However, there are a number of shortcomings in the current system:

  1. A process's size is limited by the number of TLB entries (i.e., 64 pages).
  2. kfree() never returns back freed pages to the kernel.
  3. malloc and free don't work from user programs (no sbrk system call, so no heap).
  4. When a program loads (via execv), the entire code is loaded from disk into memory.
  5. When a program is loaded (via execv), it's given a fixed amount of stack space.
  6. A process's size is limited by the amount of main memory.
  7. When a process forks, its entire address space is copied, even though, 99 times out of 100, it'll be replaced almost immediately due to an execv.
You'll be addressing all these issues by:
  1. First patch: Writing code to manage the MIPS software-managed Translation Lookaside Buffer (TLB).
  2. First patch: Keeping a global coremap to maintain the status of each frame in memory, and managing those frames.
  3. Second patch: Implementing the sbrk system call.
  4. Second patch: Implementing demand paging, where you'll load from the executable file on a demand (as-needed) basis.
  5. Second patch: Increasing the stack space allocated to a process on an as-needed basis.
  6. Final patch: Implementing swapping pages from memory to a paging disk (and vice-versa).
  7. Final patch (extra Credit): Implementing copy-on-write when copying an address-space.

Structure of the TLB entries

In the System/161 machine, each TLB entry includes a 20-bit virtual page number and a 20-bit physical page number as well as the following five fields:

All these bits/values are maintained by the operating system. When the valid bit is set, the TLB entry contains a valid translation. This implies that the virtual page is present in physical memory. A TLB miss occurs when no TLB entry can be found with a matching virtual page and address space ID (unless the global bit is set in which case the address space ID is ignored) with the valid bit set.

Things to note about the TLB:

Paging

The operating system creates the illusion of unlimited memory by using physical memory as a cache of virtual pages. Paging relaxes the requirement that all the pages in a process's virtual address space must be in physical memory. Instead, we allow a process to have pages either on disk or in memory. When the process issues an access to a page that is on disk, a page fault occurs. The operating system must retrieve the page from disk and bring it into memory. Pages with valid TLB entries are always in physical memory. This means that a reference to a page on disk will always generate a TLB fault. At the time of a TLB fault, the hardware generates a TLB exception, trapping to the operating system. The operating system then checks its own page table to locate the virtual page requested. If that page is currently in memory but wasn't mapped by the TLB, then all we need to do is update the TLB. However, the page might be on disk. If this is the case, the operating system must:

  1. Allocate a place in physical memory to store the page;
  2. Read the page from disk,
  3. Update the page table entry with the new virtual-to-physical address translation;
  4. Update the TLB to contain the new translation; and
  5. Resume execution of the user program.

Notice that when the operating system selects a location in physical memory in which to place the new page, the space may already be occupied. In this case, the operating system must evict that other page from memory. If the page has been modified or does not currently have a copy on disk, then the old page must first be written to disk before the physical page can be reallocated. If the old page has not been modified and already has a copy on disk, then the write to disk can be avoided. The appropriate page table entry must be updated to reflect the fact that the page is no longer in memory.

As with any caching system, performance of your virtual memory system depends on the policy used to decide which things are kept in memory and which are evicted. On a page fault, the kernel must decide which page to replace. Ideally, it will evict a page that will not be needed soon. Many systems (such as UNIX) avoid the delay of synchronously writing memory pages to disk on a page fault by writing modified pages to disk in advance, so that subsequent page faults can be completed more quickly.

Setup

Starting code is available at https://svn.cs.hmc.edu/cs134/spring14/given/hw4

You will undoubtedly need to add new files to the system for this assignment, e.g., kern/vm/vm.c or kern/arch/mips/mips/mipsvm.c and will end up, at some point, removing dumbvm.c. Be sure to update the file kern/conf/conf.kern, or, for machine-dependent files, kern/arch/mips/conf/conf.arch, to include any new files that you create. Take care to place files in the "correct" place, separating machine-dependent components from machine-independent components.

Now, config a VM kernel, run make depend, and build it. You are now ready to begin assignment 4.

Wiki Questions As with the previous assignment, the Wiki component is not graded in the conventional sense—completion of this part of the assignment is covered under the course's participation requirement. Every member of the class should should post or improve the answer to one of the questions below on the course's Wiki and understand the answers to all of the questions.

I have provided you with a malloc implementation (in os161: src/lib/libc/malloc.c).

Answer the following wiki questions:

  1. Assuming that a user program just accessed a piece of data at (virtual) address X, describe the conditions under which each of the following can arise. If the situation cannot happen, answer "impossible" and explain why it cannot occur.
  2. A friend of yours who foolishly decided not to take 134, but who likes OS/161, implemented a TLB that has room for only one entry, and experienced a bug that caused a user-level instruction to generate a TLB fault infinitely - the instruction never completed executing! Explain how this could happen. (Note that after OS/161 handles an exception, it restarts the instruction that caused the exception.)
  3. How many memory-related exceptions (i.e., hardware exceptions and other software exceptional conditions) can the following MIPS-like instruction raise? Explain the cause of each exception.
      # load word from $0(contains zeros) offset 0x120 into register $3
      lw  $3,0x0120($0) 
    
  4. Consider the following (useless) program:
    /* This is bad code: it doesn't do any error-checking */
    #include
    int main (int argc, char **argv) {
    int i;
    void *start, *finish;
    void *res[10];
    start = sbrk(0);
    for (i = 0; i < 10; i++) {
    	res[i] = malloc(10);
    }
    finish = sbrk(0);
            /* TWO */
            return 0;
    }
    
  5. Suppose that we now insert the following code at location /* TWO */:
            {
                    void *x;
                    free(res[8]); free(res[7]); free(res[6]);
                    free(res[1]); free(res[3]); free(res[2]);
                    x = malloc(60);  /* MARK */
            }
    
    Again on the i386, would malloc() call sbrk() when doing that last allocation at the marked line above? What can you say about x?
  6. It is conventional for libc internal functions and variables to be prefaced with "__". Why do you think this is so?
  7. The man page for malloc requires that "the pointer returned must be suitably aligned for use with any data type." How does our implementation of malloc guarantee this?

TLB Handling

In this part of the assignment, you will modify OS/161 to handle TLB faults. Additionally, you need to guarantee that the TLB state is initialized properly on a context switch. One implementation alternative is to invalidate all the TLB entries on a context switch. The entries are then re-loaded by taking TLB faults as pages are referenced. If you do this, be sure to copy any relevant state maintained by the TLB entries back into the page table before invalidating them. (For example, in order for the paging algorithm to know which pages must be written to disk before eviction, you must make sure that the information about whether a page is dirty or not is properly propagated back into the page table.) An alternative to invalidating everything is to use the 6-bit address space IDs and maintain separate processes in the TLB simultaneously.

I recommend that you separate implementation of the TLB entry replacement algorithm from the actual piece of code that handles the replacement. This will make it easy to experiment with different replacement algorithms if you wish to do so. Figure out how to use conf settings to choose your replacement algorithm by modifying the VM conf file.

Using the original dumbvm.c, no process could access more than 64 (num TLB entries) pages, because dumbvm never removed TLB entries (other than on context switch). Once you are managing the TLB better, you should be able to run large programs. I suggest testing with /testbin/huge (you may need to edit the size of the large array in huge so that the resulting program is larger than 64 pages, but small enough to fit into physical memory). At this point, as long as you have enough physical memory for the process, that program should run.

You can adjust the amount of physical memory available by editing the ramsize line in your sys161.conf file.

sbrk

You are responsible for making the malloc() we give you work; this will involve writing an sbrk() system call.

Note that the operation of malloc() and free() is a standard job interview question; read and understand the code.

You can test this with testbin/malloctest.

Demand Paging

For this part of the project, you'll need to modify how executables are loaded. Rather than reading the entire file into memory on an exec, you'll need to open the file, read the sizes, and then keep that information, along with a reference to the executable, in your PCB. When a page fault occurs, you'll need to figure out whether it's from the code portion of the executable. If so, you can dynamically load it directly from there. You can do the same the thing for the other segments as well, reading only on demand. You can test by instrumenting the number of page allocations, and then running a program that has more than one page of code in it, but which doesn't execute most of it (imagine a main that just returns). Once you've implemented demand paging, you should find the number of page allocations is reduced.

An alternative test would be to have a program with more code that will fit into physical memory; as long as the code is never executed, a dynamically-paged kernel will be able to load and run the program.

Dynamic stack allocation

dumbvm.c allocates a fixed stack space to each process. You'll want to dynamically allocate stack space instead; when a page fault occurs within the area of the address space reserved for the stack, allocate a new page. You can (and should) ensure that the stack doesn't exceed the address space reserved for it.

You won't deallocate allocate stack space (how would you know when it was safe to do so?).

Paging

In this part of the assignment, you will modify OS/161 to handle page faults. When you have completed this problem, your system will generate an exception when a process tries to access an address that is not memory-resident and then handle that exception and continue running the user process.

You will need routines to move a page from disk to memory and from memory to disk.

You will need to decide how to implement backing store (the place on disk where you store virtual pages not currently stored in physical memory). The default sys161.conf includes two disks; you can use one of those disks for swapping. Please do swap to a disk and not somewhere else (such as a file). Also, be sure not to use that disk for anything else! To help prevent errors or misunderstandings, please have your system print the location of the swap space when it boots.

You will need to store evicted pages and find them when you need them. You should maintain a bitmap that describes the space in your swap area. Think of the swap area as a collection of chunks, where each chunk holds a page. Use the bitmap to keep track of which chunks are full and which are empty. The empty chunks can be evicted into. You also need to keep track, for each page of a given address space, of which chunk in the swap area it maps onto. When there are too many pages to fit in physical memory, you can write (modified) pages out to swap.

When the time comes to bring a page into memory, you will need to know which physical pages are currently in use. One way to manage physical memory is to maintain a core map, a sort of reverse page table. Instead of being indexed by virtual addresses, a core map is indexed by its physical page number and contains the virtual address and address space identifier for the virtual page currently backed by the page in physical memory. When you need to evict a page, you look up the physical address in the core map, locate the address space whose page you are evicting and modify the corresponding state information to indicate that the page will no longer be in memory. Then you can evict the page. If the page is dirty, it must first be written to the backing store. In some systems, the writing of dirty pages to backing store is done in the background. As a result, when the time comes to evict a page, the page itself usually clean (that is, it has been written to backing store, but not modified since then). You could implement this mechanism in OS/161 by creating a thread that periodically examines pages in memory and writes them to backing store if they are dirty.

Your paging system will also need to support page allocation requests generated by kmalloc(). You should review kmalloc() to understand how these requests are generated, so that your system will respond to them correctly.

You should implement at least two page-replacement policies, so add kernel configuration options to do so and build your paging system in such a way that it is easy to use different replacement algorithms.

Copy on write

In order to handle copy-on-write, your coremap will likely need to keep track of all pages referring to a given frame (rather than just a single page). You'll also need some sort of reference count.

Test by timing a program that does lots of forks and execs.

Instrumentation and Tuning

For the final submissions, we ask you to tune the performance of your virtual memory system. Use the file performance.txt as your "lab notebook" for this section. You will undoubtedly want to implement some additional software counters. As a start, we suggest:

You should add the necessary infrastructure to maintain these statistics as well as any other statistics that you think you will find useful in tuning your system. You'll probably want to print these statistics when os161 shuts down.

Once you have completed all the problems in this assignment and added instrumentation, it is time to tune your operating system.

At a minimum, use the matmult and sort programs provided to determine your baseline performance (the performance of your system using the default TLB and paging algorithms). Experiment with different TLB and paging algorithms and parameters in an attempt to improve your performance. As before, predict what will happen as you change algorithms and parameters. Compare the measured to the predicted results; obtaining a different result from what you expect might indicate a bug in your understanding, a bug in your implementation, or a bug in your testing. Figure out where the error is! I suggest that you tune the TLB and paging algorithms separately and then together. You may not rewrite the matmult program to improve its performance.

You should add other test programs to your collection as you tune performance, otherwise your system might perform will at matrix multiplication and sorting, but little else. Try to introduce programs with some variation in their uses of the memory system.

Provide a complete summary of the performance analysis you conduct. Include a thorough explanation of the performance benefits of your optimizations. What to hand in

Design Document

Your design document must include:

Turn in your design on the wiki page.

Assignment code You'll have 3 patches to turnin: vm-a.patch, vm-b.patch, and vm-final.patch.

You should generate your patches by running

cd ~/cs134/hw4
handin/makepatch > handin/vm.patch
svn commit
In addition to your patch file, you should submit a transcript showing results of running the original kernel with a test program failing, and your kernel running with the test script succeeding (see man script for details on how to get a transcript).

This is how you'll show that you've implemented the needed features.

Finally, you'll include a .txt file that documents your system. This is also where you'll provide performance information. Strategy

Look at the code system161/src/mipseb/mips.c to see how TLB faults are generated. Then examine the vm_fault() handler in os161/src/kern/arch/mips/mips/dumbvm.c. What changes must you add to support TLB and page faults?

Some of the key issues are:

When you write test programs, think about verifying that your replacement algorithms are working correctly (i.e., "If I run this program it should generate exactly n TLB faults with your algorithm; does it?").

As your system begins to stabilize, begin to work on the performance tuning. Continue to test and fix bugs. Take turns testing and tuning. Be sure to keep a careful log of your performance experiments so you can trade it back and forth.

Rubric

The entire homework assignment 4 is worth twice that of the previous homework assignments.

Patch 1 correctness will be worth 15%

Patch 2 correctness will be worth 15%

Final patch: