CS 111 Lecture 16: Virtual Memory

Prepared by Andrew Yoon for a lecture given by Professor Paul Eggert on 11/20/13

Table of Contents

Overview: What's the advantage of VM?

Performance of VM not quite as good as RAM, and we must rely on locality of reference: if a program refers to location x, its next reference is probably x, x-1, or x+1.

Say we have 128 KiB of RAM and 16 MiB of VM.

Example of a program's memory references
16 MiB VM -----------------------------------------------
 |           |  x  |            |     |
 |           |   x |            |     |
 |           |     |            |   x |
Time         |     |            | x   |
 |           | x   |            |     |
 V           |     |            | x   |

We can store just the used portions of VM in our 128 KiB of RAM.

A problem with this is thrashing, where the 'actual' picture looks like this:

16 MiB VM -----------------------------------------------
 |           |  x  |            |     |
 |           |     |   x        |     |
 |        x  |     |            |   x |
Time         |     |            |     |    x
 |           | x   |            |  x  |
 V           |     |         x  |     |

And we don't have enough room in our RAM to store all the references. In this case I/O operations dominate paging. This is too slow! To the user it may appear that the program doesn't work at all!

Utility Graph

As we can see, VM doesn't provide too much of a performance boost.

So why use VM at all? In practice, it is used to solve the following problem.

Problem

Lots of unreliable processes with bad memory references. Examples:

Solutions

  1. Don't do that! Fire programmer who writes buggy code
  2. Every memory process is a syscall
  3. The compiler inserts code around every access, checking if pointer is valid.
    1. How does the compiler know range?
      • We can use a better language, like Java, which stores every array's size.
    2. gcc -fsanitize=address
      • The compiler inserts, around every access, a check to see if it is valid. It accomplishes this using shadow memory.
      • Each byte in shadow memory describes one 8-byte block in real memory. To find a pointer p, we use
        (p >> 3) + shadow_mem_start
        and the shadow memory will tell us if it is valid.
  4. Per-process base-bounds pair.
                Example
                RAM---------------------------------------------------------
                |  p4  |  p1  |(unused)|  p2  |  p3  |(reserved for kernel)|
                ------------------------------------------------------------
                       L1     U1
                L0     U0
    
  5. Segmented Memory
                Example Segment Table
                  | Loc | Size |
                0 |     |      |
                  |     |      |
      Segment #       .
                      .
                      .
              255 |     |      |
    
    (The location column refers to physical memory addresses.)
                RAM---------------------------------------------------------
                | p3,s3 | p3,s0 | p3,s1 | etc | p3,s2 |(reserved for kernel)|
                ------------------------------------------------------------
                
  6. Virtual Memory
                Example 1-level page table (per process)
                  -----32 bits wide----
                0 | 20 bits | 12 bits |
                      .
                      .
                      .
         2^20 - 1 |         |         |
                2^22 bytes/page table = 4 MiB/process
    

More about VM

We can have multiple level page tables, such as the doubly indirect block we used in Lab 3. For example, if we are using a 2-level page table, our virtual address could look like this:

            | 10 bits | 10 bits | 12 bits |
               "hi"      "med"     "lo"

hi points to level 1 of the table, med points to level 2.

Example implementation:

/*
 * pmap(va)
 * gives physical memory at virtual address va
 */
unsigned pmap(unsigned va) {
    unsigned hi = va >> 22;
    unsigned med = (va >> 12) & ((1 << 10) - 1);
    unsigned* l1pt = asm("%cr3" .....);
    unsigned* l2pt = l1pt[hi] &~ ((1 << 12) - 1);
    if (l2pt == FAULT)
        return FAULT;
    return l2pt[med] &~ ((1 << 12) - 1);
}

Page Faulting

The hardware traps when given a "bad" address. (There's no physical memory in the page table). The kernel takes over, and it can:

            unsigned long long (unsigned va) {
                return swapbase(va >> 12);
            }

Swap space

Say we have a page table, which points to a bunch of processes in physical memory.

            Swap space----------------------------------------------------
            |    | 1 |        | 3 | 2 |      | 5 |   | 4 | |(virtual mem)|
            --------------------------------------------------------------

Physical memory is a cache of swap space.

Allocating memory

malloc(large_array)
"Free memory is wasted memory." Back to Top